/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "gameengine.h"

#include "asteroid.h"
#include "meteorshower.h"
#include "blackhole.h"
#include "comet.h"
#include "ufo.h"
#include "soundengine.h"
#include "gameeffect.h"
#include "visualeffect.h"
#include "timermanager.h"
#include "gamelevel.h"
#include "collectible.h"
#include "hyperjumpnavigator.h"
#include "visualeffectdata.h"
#include "particledata.h"
#include "missile.h"

#include <math.h>

#include <QMatrix4x4>
#include <QtAlgorithms>

#include <QDebug>

/*!
    \class  GameEngine
    \brief  The \c GameEngine class contains the main game loop and all the necessary methods to
            keep up the actual gameplay.

    In addition to the main game loop \c GameEngine::cycleGameLoop, the GameEngine class contains 
    methods for:
    - Initializing a new game and new level
    - Managing frame data for rewind mode
    - Managing game timers
    - Controlling the miner 
    - Advancing game objects
    - Detecting collisions
    - Creating and processing effects (visual and sound)
    - Populating game with game objects
    - Handling miscellaus game events
    - Managing the game level progress 
    - Applying bomb destruction to game objects
    - Applying blackhole force gravity force effect
    - Guiding and firing missiles
    - etc
*/

const FPtype GameEngine::BlackHoleEffectDistance = 220.0f;
const FPtype GameEngine::BlackHoleForce = 0.2f;

const FPtype GameEngine::GameObjectRadiusAsteroidL = 9.0f;
const FPtype GameEngine::GameObjectRadiusAsteroidM = 6.5f;  // this should be close to 0.725 * GameObjectRadiusAsteroidL
const FPtype GameEngine::GameObjectRadiusSpaceCraft = 8.0f;
const FPtype GameEngine::GameObjectRadiusMineral = 5.0f;
const FPtype GameEngine::GameObjectRadiusUFO = 8.0f;
const FPtype GameEngine::GameObjectRadiusBomb = 8.0f;
const FPtype GameEngine::GameObjectRadiusHyperJump = 8.0f;
const FPtype GameEngine::GameObjectRadiusMissile = 6.0f;
const FPtype GameEngine::GameObjectRadiusComet = 15.0f;

// commandline flags:
//^^^^^^^^^^^^^^^^^^^^

bool GameEngine::commandlineShowBlackHole = false;
bool GameEngine::commandlineShowComet = false;

bool GameEngine::commandlineUseLocalFiles = false;
int GameEngine::commandlineStartLevel = 0;
int GameEngine::commandlineStartLives = 0;
int GameEngine::commandlineStartBombs = 0;
int GameEngine::commandlineStartHyperjumps = 0;

/*!
  \brief Constructor of \c GameEngine class.  

  Initializes the variables and calls \c GameEngine::initNewGame() to initialize a new game.

  \param rewindEnabled True if the "rewind"-mode is enabled.
  \param difficultyLevel Difficulty level of the game from settings (1 = Easy, 2 = Normal, 3 = Hard)
*/
GameEngine::GameEngine(bool rewindEnabled, int difficultyLevel) 
    : soundEngine(0)
    , timerManager(TimerManager::getInstance())
    , rewindEnabled(rewindEnabled)
    , gameScreenWidth(-1)
    , gameScreenHeight(-1)
    , gameViewRangeX(0.0)
    , gameViewRangeY(0.0)
    , gameViewRangeZ(0.0)
    , totalFrameCounter(0)
    , totalTimeCounter(0.0)
    , minerSteeringX(0.0)
    , minerSteeringY(0.0)
    , hyperjumpShakeZ(0.0)
    , miner(new SpaceCraft(totalFrameCounter, rewindEnabled))
    , minerRocketLevelX(0)
    , minerRocketCounterX(0)
    , minerRocketLevelY(0)
    , minerRocketCounterY(0)
    , blackhole(0)
    , comet(0)
    , hyperJumpNavigator(0)
    , updateSpaceShipGraphics(false)
    , minersLeft(MinersAtStartup)
    , level(0)
    , gameScore(0)
    , bombCount(0)
    , hyperJumpCount(0)
    , particle(new ParticleData(gameViewRangeX, gameViewRangeY, this))
    , asteroidMediumATranslationX(0.0)
    , asteroidMediumATranslationY(0.0)
    , asteroidMediumATranslationZ(0.0)
    , asteroidMediumBTranslationX(0.0)
    , asteroidMediumBTranslationY(0.0)
    , asteroidMediumBTranslationZ(0.0)
    , asteroidMediumCTranslationX(0.0)
    , asteroidMediumCTranslationY(0.0)
    , asteroidMediumCTranslationZ(0.0)
#ifdef SMASHMINER_CONFIG_ENABLE_TESTINGMODE 
    , skipMinerCollision(false)
#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE 
{
    initNewGame(rewindEnabled, difficultyLevel);
}

/*! Destructor of \c GameEngine class. 
 * Cleans up all the game objects
 */
GameEngine::~GameEngine()
{
    cleanUpGameObjects(true);
}

/*! Sets the miner control X-coordinate steering value.
 * Steering value is measured using rotation sensor.
 * Limits also the steering values to range [-1,+1].
 *
 * \param steeringX X-axel steering value from rotation sensor.
 */
void GameEngine::setSteeringX(FPtype steeringX)
{
    // limit the steering values to range [-1,+1].

    if (steeringX > +1.0)
        steeringX = +1.0;

    if (steeringX < -1.0)
        steeringX = -1.0;

    minerSteeringX = steeringX;
}

/*! Sets the miner control Y-coordinate steering value.
 * Steering value is measured using rotation sensor.
 * Limits also the steering values to range [-1,+1].
 *
 * \param steeringY Y-axel steering value from rotation sensor.
 */
void GameEngine::setSteeringY(FPtype steeringY)
{
    // limit the steering values to range [-1,+1].

    if (steeringY > +1.0)
        steeringY = +1.0;

    if (steeringY < -1.0)
        steeringY = -1.0;

    minerSteeringY = steeringY;
}

/*! Sets the ''shake'' value according for hyper jump control.
 * ''Shake'' value is measured using accelerator sensor.
 * 
 * \param shakeZ ''Shake'' value from accelerator sensor.
 */
void GameEngine::setShakeZ(FPtype shakeZ)
{
    if (shakeZ > 100.0f)
        hyperjumpShakeZ = 100.0f;
    else
        hyperjumpShakeZ = shakeZ;
}

void GameEngine::setScreenSize(int width, int height)
{
    gameScreenWidth = width;
    gameScreenHeight = height;
}

/*!
 *  \brief Initializes a new game.
 *
 *  Cleans up possible existing old game objects. 
 *  Initializes the game variables, creates a new miner object, initalizes a new
 *  \c GameLevel object. Initializes the particle buffer, starts the "Get Ready"-timer
 *  for starting the gameplay. The timer will not proceed until the game loop is called.
 *
 *  \param rewind True if rewind mode is enabled
 *  \param difficultyLevel Difficulty level of the game from settings (1 = Easy, 2 = Normal, 3 = Hard)
 */
void GameEngine::initNewGame(bool rewind, int difficultyLevel)
{
    rewindEnabled = rewind;

    cleanUpGameObjects(true);   // deletes also miner!

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
    cleanUpRewindFrames(true);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    timerManager->resetTimers();
    timerManager->cleanData();

    totalFrameCounter = 0;
    totalTimeCounter = 0.0;

    gameScore = 0;
    minersLeft = (commandlineStartLives > 0 ? commandlineStartLives : MinersAtStartup);

    bombCount = (commandlineStartBombs > 0 ? commandlineStartBombs : BombsAtStartup);
    hyperJumpCount = (commandlineStartHyperjumps > 0 ? commandlineStartHyperjumps : HyperjumpsAtStartup);

    // now create a new miner...

    miner = new SpaceCraft(totalFrameCounter, rewindEnabled);

    minerRocketLevelX = 0;
    minerRocketCounterX = 0;
    minerRocketLevelY = 0;
    minerRocketCounterY = 0;

    resetSpaceShip(0.0f, 0.0f);

    currentLevelProgress[0] = -1;
    currentLevelProgress[1] = -1;
    bombProbability = 0.0f;

    level = new GameLevel(difficultyLevel, this);

    if (commandlineStartLevel > 0)
        level->setCurrentLevel(commandlineStartLevel-1);

    initNextLevel();

#ifdef SMASHMINER_CONFIG_USE_OPENGL_ES_2
    emit changeBackGroundImage(commandlineStartLevel > 0 ? commandlineStartLevel : 1);
#endif

    // Initialize the particle buffer (particle buffer is used for storing particle effect data)
    for (int i = 0;i < SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY;i++) {
        particleData().particleBuffer[i].particleType = ParticleTypeNULL;
        particleData().particleBuffer[i].startFrame = -10;
        particleData().particleBuffer[i].startTime = -10.0 * SMASHMINER_CONFIG_PARTICLE_LIFETIME;
        particleData().particleBuffer[i].startPosX = -10.0 * gameViewRangeX;
        particleData().particleBuffer[i].startPosY = -10.0 * gameViewRangeY;
        particleData().particleBuffer[i].startVelX = 0.0;
        particleData().particleBuffer[i].startVelY = 0.0;
    }

    particleData().particleWriteIndex = 0;

    if (commandlineShowBlackHole) {
        // force a blackhole object due to a commandline option.
        // NOTE : gameViewRangeX and gameViewRangeY are not yet defined.
        blackhole = new BlackHole(totalFrameCounter, rewindEnabled, 20.0f);
        blackhole->setPosX(-VIEW_RANGE_XY * 0.50f);
        blackhole->setPosY(+VIEW_RANGE_XY * 0.25f);
        blackhole->setVelX(+0.75f);
        blackhole->setVelY(-0.25f);
    }

    if (commandlineShowComet) {
        // force a comet object due to a commandline option.
        // NOTE : gameViewRangeX and gameViewRangeY are not yet defined.
        comet = new Comet(totalFrameCounter, rewindEnabled, totalTimeCounter, 4.0f, 10.0f, 2.0f);
        comet->setPosX(+VIEW_RANGE_XY * 0.50f);
        comet->setPosY(-VIEW_RANGE_XY * 0.25f);
        const GLfloat cometAngle = 290.0f * M_PI / 180.0f;
        comet->setRotationAngle(cometAngle);
        comet->setVelX(sin(cometAngle) * 4.0f);
        comet->setVelY(cos(cometAngle) * 4.0f);
    }

    // a new game is always started by going into GetReady state.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // but in a TESTINGMODE we go straight into the normal game mode.

#ifndef SMASHMINER_CONFIG_ENABLE_TESTINGMODE
    timerManager->startGameTimer(GameTimer::stateGetReadyTimer);
#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE
}

/*!
 * \brief The main game loop of the SmashMiner-game.  
 * 
 * Controls the game events, e.g.:
 * - Initializes new frame data for rewind mode (\c GameEngine::initializeFrame()). 
 * - Updates the game timers and takes action regarding the state of running timers (\c GameEngine::handleGameTimers()). 
 * - Advances and controls game objects (\c GameEngine::advanceGameObjects(), \c GameEngine::controlSpaceShip())
 * - Adds particle effects (\c GameEngine::addSpaceShipSteeringEffects(), \c
 *   GameEngine::addMissileParticleEffects(), \c GameEngine::addCometParticleEffects(), \c
 *   GameEngine::addBlackHoleParticles(), \c GameEngine::addHyperJumpParticles())  
 * - Detectes collisions between game objects (\c GameEngine::detectCollisions()). 
 * - Handles misc game events (\c GameEngine::controlGameEvents())
 * - Removes expired game objects (\c GameEngine::removeUnnecessaryObjects())
 * - If rewind is enabled, stores object positions for rewind mode playback (\c
 *   GameEngine::writeObjectPositions()).
 * - Process the game effects enqueued during the current game cycle (\c
 *   GameEngine::processNewEffects()).
 *
 * \param timestep A timestep calculated for current game cycle (current game frame length) in milliseconds.
 */
void GameEngine::cycleGameLoop(FPtype timestep)
{
    const int timeStepAsInt = ceil(timestep);

    initializeFrame(timestep);
    currentFrameStats.msecs = timeStepAsInt;

    handleGameTimers(timestep);

    bool blockSteering = false;
    if (timerManager->isActive(GameTimer::stateGetReadyTimer)) blockSteering = true;
    if (timerManager->isActive(GameTimer::hyperJumpOutTimer)) blockSteering = true;
    if (timerManager->isActive(GameTimer::hyperJumpInTimer)) blockSteering = true;

    if (miner->isAlive() && !blockSteering) {
        controlSpaceShip(timestep);
        addSpaceShipSteeringEffects(timeStepAsInt);

        // accelerometer z-steering.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // hyperjumpShakeZ is between 0-100.

        if (hyperjumpShakeZ > 50) {
            bool blockHyperJump = false;

            // test that the effect is not already running.
            if (hyperJumpNavigator != NULL) blockHyperJump = true;
            if (TimerManager::hyperJumpOutData != NULL) blockHyperJump = true;
            if (TimerManager::hyperJumpInData != NULL) blockHyperJump = true;

            // test that we are not in an unsuitable game state.
            if (timerManager->isActive(GameTimer::stateGetReadyTimer)) blockHyperJump = true;
            if (timerManager->isActive(GameTimer::stateLevelChangingTimer)) blockHyperJump = true;
            if (timerManager->isActive(GameTimer::statePlayerCrashedTimer)) blockHyperJump = true;
            if (timerManager->isActive(GameTimer::stateGameOverTimer)) blockHyperJump = true;

            if (!blockHyperJump && hyperJumpCount > 0) {
                shipHyperJumpOut();     // Launches HyperJump!
                hyperJumpCount--;
            }
        }
    }

    advanceGameObjects(timestep);

    // add particle effects for missiles.

    if (enemyList.count() > 0)
        addMissileParticleEffects(timeStepAsInt);

    // if a comet object exists, then launch
    // particle objects to visualize it.

    if (comet != NULL && comet->isAlive())
        addCometParticleEffects(timeStepAsInt);

    // if a blackhole object exists, then launch
    // special particle objects to visualize it.

    if (blackhole != NULL && blackhole->isAlive())
        addBlackHoleParticles(timeStepAsInt);

    // if we have hyperjump effect(s) running, then
    // add special particle objects to visualize them.

    if (TimerManager::hyperJumpOutData != NULL || TimerManager::hyperJumpInData != NULL)
        addHyperJumpParticles(timeStepAsInt);

    // at this stage, all existing GameObjects should
    // have their final positions for this cycle.

    detectCollisions();
    controlGameEvents(timestep);

    // at this stage, due to either object collisions
    // or random game events, some objects might have
    // marked as "dead" -> clean them away as soon
    // as possible (but keeping them if needed
    // for a possible rewind animation).

    removeUnnecessaryObjects();

    // at this stage, all GameObject management
    // (including addition of new and deletion
    // of unnecessary ones) should be done.

    if (hyperJumpNavigator != NULL) {
        // If HyperJump is happening try to get better estimate for the new location
        // NOTE! This should be done after all GameObject management is done!
        hyperJumpNavigator->refine(totalTimeCounter, asteroidList, collectibleList, enemyList, 
                                   blackhole, comet);
    }

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // now store the positions for rewinding
    // for all existing GameObjects.

    if (rewindEnabled)
        writeObjectPositions();

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    processNewEffects();

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // now we are ready to return and later paint the game state
    // to the screen -> store the current spaceship mode into the
    // frameinfo object for later use in rewind mode setup.

    FrameInfo::StoredSpaceShipMode m;

    if (timerManager->time(GameTimer::shipBall2ConeTimer) >= 0)
        m = FrameInfo::ConeMode;
    else if (timerManager->time(GameTimer::shipCone2BallTimer) >= 0)
        m = FrameInfo::BallMode;
    else if (miner->mode() == SpaceCraftBallMode)
        m = FrameInfo::BallMode;
    else
        m = FrameInfo::ConeMode;

    frameInfoQ.back()->setMode(m);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
}

/*!
 * \brief Initializes a new \c FrameInfo object for rewind mode.
 *
 * \c FrameInfo objects are stored in the \c frameInfoQ queue for rewind mode for each game frame.
 * Initializes and queues a new \c FrameInfo object and purges the obsoleted \c FrameInfo objects
 * to keep the queue size reasonable. Keeps up the total frame count and total time.
 * The given timestep is appended to total time.
 *
 * \param timestep A timestep in milliseconds (calculated frame length).
 */
void GameEngine::initializeFrame(FPtype timestep)
{
    // advance frame and time counters (time measured in seconds).
    // "timestep" is in milliseconds while totalTimeCounter is in seconds.

    totalFrameCounter++;
    totalTimeCounter += timestep / 1000.0f;     // ms to s conversion.

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // create a new FrameInfo object for this frame,
    // and store it into the queue.

    FrameInfo * newFI = new FrameInfo(totalFrameCounter, totalTimeCounter, timestep);
    frameInfoQ.enqueue(newFI);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // reset statistics for the current frame.

    currentFrameStats.reset();

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (frameInfoQ.empty())
        qFatal("GameEngine::initializeFrame() : frameInfoQ is empty.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // purge the obsoleted FrameInfos to keep the queue size reasonable.

    while (totalTimeCounter - frameInfoQ.head()->timeStamp() 
        > SMASHMINER_CONFIG_REWIND_PERIOD_SECONDS) {
        FrameInfo *oldFI = frameInfoQ.dequeue();
        delete oldFI;
    }

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
}

/*! Updates game timers and takes actions according to timer states.
 *
 * In rewind mode playback, \c GameWidget::cycleRewindMode() is an
 * equivalent to this method, but it only does a selected set of
 * tasks so that effects and animations are played but the
 * game state is not altered and the game can go on right after
 * the playback has stopped.
 *
 * \param timestep A timestep in milliseconds (calculated frame length).
 */
void GameEngine::handleGameTimers(FPtype timestep)
{
    timerManager->updateTimers(timestep);

    // handle game state timers.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^

    if (timerManager->time(GameTimer::stateGetReadyTimer) >= 0) {
        if (timerManager->isHalfTime(GameTimer::stateGetReadyTimer)) {

            // start hyperjump-in navigation for bringing the player to the screen.

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            if (hyperJumpNavigator != NULL)
                qFatal("GameEngine::shipHyperJumpOut() : hyperJumpNavigator != NULL");
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // hyperJumpInTime is the time (in seconds) when the HyperJumpIn
            // effect will be started. it is computed by converting the timer length
            // (in milliseconds) to seconds and dividing the result further by 2 since
            // the HyperJumpIn effect will be started at full-time of the GetReady timer
            // (see below) and we are now at the half-time of the very same timer.

            const FPtype hyperJumpInTime = (FPtype) timerManager->timerLength(GameTimer::stateGetReadyTimer) / 2000.0f;

            hyperJumpNavigator = new HyperJumpNavigator(totalTimeCounter + hyperJumpInTime, gameViewRangeX, gameViewRangeY);
        }

        if (timerManager->isFullTime(GameTimer::stateGetReadyTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::stateGetReadyTimer);

            timerManager->startGameTimer(GameTimer::moreObjectsTimer);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qDebug() << "stateGetReadyTimer : FullTime -> starting shipHyperJumpIn()";
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            shipHyperJumpIn(true);
        }
    }

    if (timerManager->time(GameTimer::stateLevelChangingTimer) >= 0) {
        if (timerManager->isFirstQuarterTime(GameTimer::stateLevelChangingTimer)) 
            emit changeBackGroundImage(level->currentLevel() + 1);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        if (timerManager->isHalfTime(GameTimer::stateLevelChangingTimer))
            cleanUpRewindFrames(true);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

        if (timerManager->isFullTime(GameTimer::stateLevelChangingTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::stateLevelChangingTimer);

            initNextLevel();
        }
    }

    if (timerManager->time(GameTimer::statePlayerCrashedTimer) >= 0) {
        if (timerManager->isFullTime(GameTimer::statePlayerCrashedTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::statePlayerCrashedTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            if (rewindEnabled) {
                emit playerCrashed();   // go into rewind mode...
            } else {
                if (minersLeft < 1) // go to either GetReady or GameOver state.
                    timerManager->startGameTimer(GameTimer::stateGameOverTimer);
                else
                    timerManager->startGameTimer(GameTimer::stateGetReadyTimer);
            }
#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
            if (minersLeft < 1) // go to either GetReady or GameOver state.
                timerManager->startGameTimer(GameTimer::stateGameOverTimer);
            else
                timerManager->startGameTimer(GameTimer::stateGetReadyTimer);
#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
        }
    }

    if (timerManager->time(GameTimer::stateGameOverTimer) >= 0) {
        if (timerManager->isFirstQuarterTime(GameTimer::stateGameOverTimer))
            newSoundEffect(SoundGameOver);

        if (timerManager->isFullTime(GameTimer::stateGameOverTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::stateGameOverTimer);

            emit gameOver(gameScore, totalTimeCounter);
        }
    }

    // handle other timers.
    //^^^^^^^^^^^^^^^^^^^^^^

    if (timerManager->time(GameTimer::shipSafeTimer) >= 0) {
        if (timerManager->isFullTime(GameTimer::shipSafeTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::shipSafeTimer);
        }
    }

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    const bool timer1 = timerManager->isActive(GameTimer::shipBall2ConeTimer);
    const bool timer2 = timerManager->isActive(GameTimer::shipCone2BallTimer);
    if (timer1 && timer2)
        qFatal("GameEngine::handleGameTimers() : both spaceship-mode-timers active.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (timerManager->time(GameTimer::shipBall2ConeTimer) >= 0) {
        if (timerManager->isActive(GameTimer::shipBall2ConeTimer)) {
            const GLfloat relTime = (GLfloat) timerManager->timeRel(GameTimer::shipBall2ConeTimer);
            miner->setTransformationStatus(1.0f - relTime);
        }

        if (timerManager->isHalfTime(GameTimer::shipBall2ConeTimer))
            miner->setMode(SpaceCraftConeMode);

        if (timerManager->isFullTime(GameTimer::shipBall2ConeTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::shipBall2ConeTimer);

            setShipModeImmediate(SpaceCraftConeMode);
        }
    }

    if (timerManager->time(GameTimer::shipCone2BallTimer) >= 0) {
        if (timerManager->isActive(GameTimer::shipCone2BallTimer)) {
            const GLfloat relTime = (GLfloat) timerManager->timeRel(GameTimer::shipCone2BallTimer);
            miner->setTransformationStatus(relTime);
        }

        if (timerManager->isHalfTime(GameTimer::shipCone2BallTimer))
            miner->setMode(SpaceCraftBallMode);

        if (timerManager->isFullTime(GameTimer::shipCone2BallTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::shipCone2BallTimer);

            setShipModeImmediate(SpaceCraftBallMode);
        }
    }

    if (timerManager->time(GameTimer::hyperJumpOutTimer) >= 0) {
        if (timerManager->isFirstQuarterTime(GameTimer::hyperJumpOutTimer))
            TimerManager::hyperJumpOutData->setTransforming(true);

        if (timerManager->isHalfTime(GameTimer::hyperJumpOutTimer))
            shipHyperJumpIn(false);

        if (timerManager->isLastQuarterTime(GameTimer::hyperJumpOutTimer)) {
            TimerManager::hyperJumpOutData->setTransforming(false);
            TimerManager::hyperJumpOutData->setHidden(true);
        }

        if (timerManager->isFullTime(GameTimer::hyperJumpOutTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::hyperJumpOutTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::hyperJumpOutData == NULL)
                qFatal("hyperJumpOutTimer : hyperJumpOutData == NULL");

            if (TimerManager::hyperJumpOutData->isRewind())
                qFatal("hyperJumpOutTimer : hyperJumpOutData : rewind = true");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // cleanup the data storage...

            delete TimerManager::hyperJumpOutData;
            TimerManager::hyperJumpOutData = NULL;
        }

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

        if (timerManager->time(GameTimer::hyperJumpOutTimer) >
                timerManager->timerLength(GameTimer::hyperJumpOutTimer) + 500) {
            if (TimerManager::hyperJumpOutData != NULL)
                qFatal("ERROR : hyperJumpOut not finalized!");
        }

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    }

    if (timerManager->time(GameTimer::hyperJumpInTimer) >= 0) {
        if (timerManager->isFirstQuarterTime(GameTimer::hyperJumpInTimer)) {
            TimerManager::hyperJumpInData->setTransforming(true);
            TimerManager::hyperJumpInData->setHidden(false);
        }

        if (timerManager->isLastQuarterTime(GameTimer::hyperJumpInTimer))
            TimerManager::hyperJumpInData->setTransforming(false);

        if (timerManager->isFullTime(GameTimer::hyperJumpInTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::hyperJumpInTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::hyperJumpInData == NULL)
                qFatal("hyperJumpInTimer : hyperJumpInData == NULL");

            if (TimerManager::hyperJumpInData->isRewind())
                qFatal("hyperJumpInTimer : hyperJumpInData : rewind = true");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            // cleanup the data storage...

            delete TimerManager::hyperJumpInData;
            TimerManager::hyperJumpInData = NULL;
        }

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

        if (timerManager->time(GameTimer::hyperJumpInTimer) >
                timerManager->timerLength(GameTimer::hyperJumpInTimer) + 500) {
            if (TimerManager::hyperJumpInData != NULL)
                qFatal("ERROR : hyperJumpIn not finalized!");
        }

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    }

    if (timerManager->time(GameTimer::bombEffectTimer) >= 0) {
        if (timerManager->isActive(GameTimer::bombEffectTimer))
            applyBombDestruction();

        if (timerManager->isFullTime(GameTimer::bombEffectTimer)) {
            // mark the task as processed by stopping the timer.
            timerManager->stopGameTimer(GameTimer::bombEffectTimer);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (TimerManager::bombEffectData == NULL)
                qFatal("bombEffectTimer : bombEffectData == NULL");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            delete TimerManager::bombEffectData;
            TimerManager::bombEffectData = NULL;
        }
    }
}

/*! Sets the miner's velocity based on steering information.
 *
 * \param timestep A timestep in milliseconds (calculated frame length).
 */
void GameEngine::controlSpaceShip(FPtype timestep)
{
    // change the miner's velocity based on steering information.

    // the value 0.05 is just a suitable integration constant
    // determined with trial-and-error for comfortable steering.

    miner->setVelX(miner->velX() + timestep * minerSteeringX * 0.05f);
    miner->setVelY(miner->velY() + timestep * minerSteeringY * 0.05f);

    miner->checkSpeed();
}

/*! Advances all the game objects according to given timestep.
 *
 * To add new enemies set instructions for your enemy type in switch-case clause in \c enemyList
 * loop, e.g.
 *
 * \code
 * switch (go->objectType()) {
 *     case TypeUfo:
 *         u = static_cast<Ufo *>(go);
 *         u->doUfoSteering(timestep);
 *         integrateMotion(u, timestep);
 *         u->integrateRotation(timestep);
 *         u = NULL;
 *         break;
 *     ...
 * }
 * \endcode
 *
 * \c GameEngine::integrateMotion sets a new location for game object.
 * If the game object is a \c RotatingObject, \c RotatingObject::integrateRotation() 
 * is used to integarte rotation. In the example above an additional steering effect is made before 
 * relocating the object.
 *
 * Integrates also the effect of black hole ''gravity'' on game objects if 
 * a black hole exists using \c GameEngine::doBlackHoleEffect(). Checks also if 
 * game objects were sucked into the black hole.
 *
 * Checks if the game objects are wrapping from side of the screen to opposite side using 
 * \c GameObject::checkWrapping().
 * If the miner has wrapped starts a ''ship safe'' timer (\c GameTimer::shipSafeTimer). 
 * Miner movement is blocked if ''Get Ready'' timer is active or miner is executing a hyper jump.
 *
 * The life time of collectibles is checked. Current progress is incremented when a collectible
 * expires and the collectible is set dead:
 *
 * \code
 * if (!c->lifeCycleLeft(totalFrameCounter) && c->objectType() == TypeCollectible) {
 *      currentLevelProgress[0] += ProgressOther;
 *      setObjectDead(c, totalFrameCounter, totalTimeCounter);
 * }
 * \endcode
 *
 * \param timestep A timestep in milliseconds (calculated frame length).
 */
void GameEngine::advanceGameObjects(FPtype timestep)
{
    const bool doBlackHoleEffectFlag = (blackhole != NULL && blackhole->isAlive());

    // update the miner's position and check the wrapping status.

    if (miner->isAlive()) {
        currentFrameStats.numObjects++;

        bool doBlackHoleEffectForMiner = doBlackHoleEffectFlag;
        if (doBlackHoleEffectForMiner && isPlayerProtected())
            doBlackHoleEffectForMiner = false;

        if (doBlackHoleEffectForMiner) {
            doBlackHoleEffect(miner);
            if (!miner->isAlive()) {
                // miner got sucked into the black hole.
                handlePlayerDies();
            }
        }

        bool blockMovement = false;
        if (timerManager->isActive(GameTimer::stateGetReadyTimer)) 
            blockMovement = true;
        if (TimerManager::hyperJumpOutData != NULL && TimerManager::hyperJumpOutData->isHidden()) 
            blockMovement = true;
        if (TimerManager::hyperJumpInData != NULL && TimerManager::hyperJumpInData->isHidden()) 
            blockMovement = true;

        bool minerWrapped = false;

        if (!blockMovement) {
            integrateMotion(miner, timestep);
            minerWrapped = miner->checkWrapping(gameViewRangeX, gameViewRangeY);
        }

        if (minerWrapped) {
            // start GameTimer::shipSafeTimer which protects the
            // spaceship for blackhole forces during a short period of time.
            timerManager->startGameTimer(GameTimer::shipSafeTimer);
        }
        else if (timerManager->time(GameTimer::shipSafeTimer) >= 0) {
            // check whether we should stop GameTimer::shipSafeTimer
            // (the spaceship is no longer touching any screen edges).
            if (miner->wrappingFlags() == 0) {
                timerManager->stopGameTimer(GameTimer::shipSafeTimer);
            }
        }
    }

    // advance asteroid and collectible objects (with wrapping).

    const int astCount = asteroidList.count();
    for (int i = 0;i < astCount; i++) {
        Asteroid *a = asteroidList[i];

        if (!a->isAlive())
            continue;

        currentFrameStats.numObjects++;

        if (doBlackHoleEffectFlag)
            doBlackHoleEffect(a);

        a->integrateRotation(timestep);

        integrateMotion(a, timestep);
        a->checkWrapping(gameViewRangeX, gameViewRangeY);
    }

    const int colCount = collectibleList.count();
    for (int i = 0; i < colCount; i++) {
        Collectible *c = collectibleList[i];

        if (!c->isAlive())
            continue;

        if (!c->lifeCycleLeft(totalFrameCounter)) {
            if (c->objectType() == TypeCollectibleBomb) {
                launchBombSelf(c->posX(), c->posY());
            }
            else if (c->objectType() == TypeCollectibleMineralA
                    || c->objectType() == TypeCollectibleMineralB
                    || c->objectType() == TypeCollectibleMineralC
                    || c->objectType() == TypeCollectibleMineralD) {
                createParticleExplosion(c->posX(), c->posY(), 15, 0.025);
                currentLevelProgress[0] += ProgressOther;
            }

            setObjectDead(c, totalFrameCounter, totalTimeCounter);
            continue;
        }

        currentFrameStats.numObjects++;

        if (doBlackHoleEffectFlag)
            doBlackHoleEffect(c);

        if (c->objectType() == TypeCollectibleMineralA 
                || c->objectType() == TypeCollectibleMineralB 
                || c->objectType() == TypeCollectibleMineralC 
                || c->objectType() == TypeCollectibleMineralD) { 
            Mineral *m = static_cast<Mineral *>(c);
            if (m) m->integrateRotation(timestep);
        }

        integrateMotion(c, timestep);
        c->checkWrapping(gameViewRangeX, gameViewRangeY);
    }

    // advance the enemy objects.

    const int eneCount = enemyList.count();
    for (int i = 0; i < eneCount; i++) {
        GameObject *go = enemyList[i];

        if (!go->isAlive())
            continue;

        currentFrameStats.numObjects++;

        if (doBlackHoleEffectFlag)
            doBlackHoleEffect(go);

        Ufo *u = NULL;
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        MeteorShower *ms = NULL;
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        switch (go->objectType()) {
            case TypeUfo:
                u = static_cast<Ufo *>(go);
                u->doUfoSteering(timestep);
                integrateMotion(u, timestep);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                // checkPosition() does a position sanity check (75% margins used)
                // similarly as checkWrapping() does above for WrappingGameObjects.
                checkPosition(u, gameViewRangeX * 0.75f, gameViewRangeY * 0.75f);
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                u->integrateRotation(timestep);
                u = NULL;
                break;

            case TypeMissile:
                integrateMotion(go, timestep);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                // checkPosition() does a position sanity check (75% margins used)
                // similarly as checkWrapping() does above for WrappingGameObjects.
                checkPosition(go, gameViewRangeX * 0.75f, gameViewRangeY * 0.75f);
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                break;

            case TypeMeteorShower:
                integrateMotion(go, timestep);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                // checkPosition() does a position sanity check (75% margins used)
                // similarly as checkWrapping() does above for WrappingGameObjects.

                // BUT because we add MeteorShower enemies as large clusters, which
                // may initially (by design) extend over the normally allowed ranges,
                // we want to do the check only after the objects have passed their
                // trajectories center points as tested by the extra test below.

                ms = static_cast<MeteorShower *>(go);
                if (ms->centerPointPassed())
                    checkPosition(go, gameViewRangeX * 0.75f, gameViewRangeY * 0.75f);
                ms = NULL;
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
                break;

            default:
                qFatal("GameEngine::advanceGameObjects() : unknown enemy type %d", go->objectType());
        }
    }

    // advance the blackhole object if it exists.

    if (blackhole != NULL && blackhole->isAlive()) {
        integrateMotion(blackhole, timestep);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        // checkPosition() does a position sanity check (75% margins used)
        // similarly as checkWrapping() does above for WrappingGameObjects.
        checkPosition(blackhole, gameViewRangeX * 0.75f, gameViewRangeY * 0.75f);
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

        currentFrameStats.numObjects++;
    }

    // advance the comet object if it exists.

    if (comet != NULL && comet->isAlive()) {
        if (doBlackHoleEffectFlag)
            doBlackHoleEffect(comet);

        integrateMotion(comet, timestep);
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        // checkPosition() does a position sanity check (75% margins used)
        // similarly as checkWrapping() does above for WrappingGameObjects.

        // for the comet test, extend the default margins by comet length.

        const FPtype cl = comet->getLength(totalTimeCounter);
        checkPosition(comet, gameViewRangeX * 0.75f + cl, gameViewRangeY * 0.75f + cl);
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

        currentFrameStats.numObjects++;
    }
}

/*! Used for testing if collision has occured between game object A and B.
 * Each game object has a radius which is used in collision testing.
 * First a rough collision testing is done by comparing X and Y locations of game objects.
 * X- and Y-distance and a ''collision distance'' is used in the collision testing.
 *
 * X-distance is the difference between X-coordinates:
 * \code
 * distanceX = fabs(gameObjectA->posX() - gameObjectB->posX());
 * \endcode
 * Y-distance is the difference between Y-coordinates accordingly.
 *
 * The ''collision distance'' is a sum of radius' of game object A and B:
 * \code
 * collisionDistance = gameObjectA->objectRadius() + gameObjectB->objectRadius();
 * \endcode
 *
 * In this case collision might occur, so more precise collision testing is done:
 * \code
 * if (distanceX < collisionDistance && distanceY < collisionDistance) {
 *      // ... 
 * }
 * \endcode
 *
 * In more precise collision testing the sum of squares of X- and Y-distances is compared to 
 * square of ''collision distance'' as in Pythagorean theorem \f$c^2 = a^2 + b^2\f$:
 * \code
 *  if (distanceX * distanceX + distanceY * distanceY < collisionDistance * collisionDistance) {
 *      // ...
 *  }
 * \endcode
 *
 * \param gameObjectA A game object
 * \param gameObjectB A game object
 * \return True if collision is detected.
 */
bool GameEngine::detect(const GameObject *gameObjectA, const GameObject *gameObjectB) const
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!gameObjectA || !gameObjectB)
        qFatal("GameEngine::detect()) : received NULL pointer(s).");

    if (!gameObjectA->isAlive() || !gameObjectB->isAlive())
        qFatal("GameEngine::detect() : received dead object(s).");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    const FPtype collisionDistance = gameObjectA->objectRadius() + gameObjectB->objectRadius();

    // Check first roughly if collision is possible:

    const FPtype distanceX = fabs(gameObjectA->posX() - gameObjectB->posX());
    if (distanceX < collisionDistance) {

        const FPtype distanceY = fabs(gameObjectA->posY() - gameObjectB->posY());
        if (distanceY < collisionDistance) {

            // More precise collision testing:

            if (distanceX * distanceX + distanceY * distanceY < collisionDistance * collisionDistance)
                return true;
        }
    }

    return false;
}

/*! Main entry point for collision detection between game objects.
 *
 * First asteroid list will be iterated and asteroid to asteroid collisions are tested.
 *
 * Before testing collision between game objects, it's tested if a game object is ''alive''.
 * If the rewind mode is enabled a game object may exist in ''dead'' state waiting for the
 * possible rewind playback. If a game object is not ''alive'' it's not rendered to game 
 * screen and there's no use for collision testing:
 *
 * \code
 *    for (int j = i + 1; j < astCount; j++) {
 *      Asteroid *aj = asteroidList[j];
 *      if (!aj->isAlive())
 *          continue;
 *      // ...
 *    }
 * \endcode
 *
 * The actual collision detection is done by calling the \c GameEngine::detect() method with
 * the two game objects being tested as arguments:
 *
 * \code
 * if (detect(ai, aj) && aj->isCollidable(totalFrameCounter))
 *      handleBumpCollision(ai, aj);
 * \endcode
 *
 * The \c GameObject::isCollidable() method is used for ensuring that a new collision between game
 * objecs is not too soon. A new object has collision free frames --- during the collision free
 * frames the \c GameObject::isCollidable() method returns false and collision will not be handled.
 *
 * If miner is alive then collision between miner and other game objects is tested. 
 * If miner is in cone mode and has collided with a collectible the 
 * \c GameEngine::handleMinerCollectibleCollision() is called.  
 *
 * If miner collides with an asteroid the \c GameEngine::handleMinerAsteroidCollision() 
 * method is called.
 * If miner collides with an enemy object (Ufo, meteor shower, comet, missile)
 * \c GameEngine::handlePlayerCrash() is called.
 */
void GameEngine::detectCollisions()
{
    // Asteroid to asteroid collision detection.

    const int astCount = asteroidList.count();
    for (int i = 0; i < astCount - 1; i++) {
        Asteroid *ai = asteroidList[i];
        if (!ai->isAlive())
            continue;

        for (int j = i + 1; j < astCount; j++) {
            Asteroid *aj = asteroidList[j];
            if (!aj->isAlive())
                continue;

            // purpose of the isCollidable() test is to
            // make sure that the large asteroid splitting
            // animation will run smoothly.

            if (detect(ai, aj) && ai->isCollidable(totalFrameCounter, aj)) {

                // if either asteroid has the wrappingDisabled
                // flag set, we must here unset it immediately ->
                // otherwise the asteroids can escape from screen.

                ai->setWrappingDisabled(false);
                aj->setWrappingDisabled(false);

                handleBumpCollision(ai, aj);
            }
        }
    }

    // if the miner either has died or is
    // protected, then stop further testing.

    if (!miner->isAlive())
        return;

    // miner-specific collision testing:
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    // Miner to collectibles collision detection.

    const int colCount = collectibleList.count();
    for (int i = 0; i < colCount; i++) {
        Collectible *col = collectibleList[i];
        if (!col->isAlive())
            continue;

        // purpose of the isCollidable() test is to
        // make sure that miner won't collide immediately
        // with newly created collectibles.

        if (detect(miner, col) && col->isCollidable(totalFrameCounter)) {
            if (miner->mode() == SpaceCraftConeMode)
                handleMinerCollectibleCollision(i);
            else
                handleBumpCollision(miner, col);
        }
    }

#ifdef SMASHMINER_CONFIG_ENABLE_TESTINGMODE
    if (skipMinerCollision)
        return;
#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE

    const bool safe = isPlayerProtected();

    // Miner to asteroid collision detection.

    for (int i = astCount - 1; i >= 0; --i) {
        Asteroid * ai = asteroidList[i];
        if (!ai->isAlive())
            continue;

        if (!miner->isAlive())  // miner might have died
            break;

        // purpose of the isCollidable() test is to
        // make sure that miner won't collide immediately
        // with newly created medium asteroids.

        if (detect(miner, ai) && ai->isCollidable(totalFrameCounter)) {

            // if the asteroid has the wrappingDisabled
            // flag set, we must here unset it immediately ->
            // otherwise the asteroid can escape from screen.

            ai->setWrappingDisabled(false);

            handleMinerAsteroidCollision(i);
        }
    }

    if (!miner->isAlive() || safe)
        return;

    // Miner - enemy collision detection.

    const int eneCount = enemyList.size();
    for (int i = eneCount - 1; i >= 0; --i) {
        GameObject *go = enemyList[i];
        if (!go->isAlive()) 
            continue;

        if (!miner->isAlive())  // miner might have died
            break;

        if (detect(miner, go)) {
            if (go->objectType() == TypeMissile) {
                setObjectDead(go, totalFrameCounter, totalTimeCounter);
                createParticleExplosion(go->posX(), go->posY(), 30);
            }

            handlePlayerCrash(SoundExplosion);
        }
    }

    // Miner - Comet special collision test.

    if (comet != NULL && comet->isAlive()) {
        if (comet->testCollision(miner->posX(), miner->posY(), totalTimeCounter))
            handlePlayerCrash(SoundExplosion);
    }
}

/*! Checks if the miner is in protected state. Miner is in protected state
 * if one of the following timers is running:
 * - \c GameTimer::shipSafeTimer
 * - \c GameTimer::hyperJumpOutTimer
 * - \c GameTimer::hyperJumpInTimer
 * - \c GameTimer::stateGetReadyTimer
 * - \c GameTimer::stateLevelChangingTimer
 *   
 * \returns True if miner is in protected state.
 */
bool GameEngine::isPlayerProtected(void)
{
    if (timerManager->time(GameTimer::shipSafeTimer) >= 0)
        return true;

    if (timerManager->time(GameTimer::hyperJumpOutTimer) >= 0)
        return true;

    if (timerManager->time(GameTimer::hyperJumpInTimer) >= 0)
        return true;

    if (timerManager->time(GameTimer::stateGetReadyTimer) >= 0)
        return true;

    if (timerManager->time(GameTimer::stateLevelChangingTimer) >= 0)
        return true;

    return false;
}

/*! Creates a ''bump''-collision effect between two colliding game objects A and B.
 *
 * In a ''bump''-collision an inelastic collision (like two steel balls hitting together)
 * is imitated. The momentums of the objects are exchanged, while the 
 * sum of all velocities is maintained.
 *
 * First total velocities of objects are calculated. The total velocity is calculated using
 * the Pythagorean theorem \f$c = \sqrt{a^2 + b^2}\f$ , e.g. for calculating the total velocity for object A:
 * \code
 * totalVelocityA = sqrt(gameObjectA->velX() * gameObjectA->velX() + gameObjectA->velY() * gameObjectA->velY());
 * \endcode
 *
 * If the total velocities of A and B are too small the inelastic collision model fails. A minimal total
 * velocity is assigned for game objects.
 *
 * Collision vector and collision vector length is calculated from positions of A and B:
 * \code
 * collisionVectorX = gameObjectB->posX() - gameObjectA->posX();
 * collisionVectorY = gameObjectB->posY() - gameObjectA->posY();
 * collisionVectorLength = sqrt(collisionVectorX * collisionVectorX + collisionVectorY * collisionVectorY);
 * \endcode
 *
 * If the collision vector length is near zero, a new randomized collision vector is created to
 * prevent possible risk of zero division: 
 * \code
 * collisionVectorX = getRandomF() - 0.50f;
 * collisionVectorY = getRandomF() - 0.50f;
 * collisionVectorLength = sqrt(collisionVectorX * collisionVectorX + collisionVectorY * collisionVectorY);
 * \endcode
 *
 * Collision vector is calculated to a unit vector: 
 * \code
 * collisionVectorX /= collisionVectorLength;
 * collisionVectorY /= collisionVectorLength;
 * \endcode
 *
 * Each game object has a mass value. A momentum impact is appended on velocities as based on conservation 
 * of momentum as in 
 * 
 * \f$m_1v_1 + m_2v_2 = m_1v_1' + m_2v_2'\f$
 *
 * and conservation of kinetic energy as in
 *
 * \f$m_1v_1^2 + m_2v_2^2 = m_1v_1'^2 + m_2v_2'^2\f$
 *
 * Final velocities are calculated as in
 *
 * \f$v_1' = (2m_2v_2 + v_1(m_1-m_2))/(m_1+m_2)\f$
 *
 * \f$v_2' = (2m_2v_1 + v_2(m_1-m_2))/(m_1+m_2)\f$
 *
 * \code
 * massA = gameObjectA->mass();
 * massB = gameObjectB->mass();
 * finalTotalVelocityA = (2.0f * massB * totalVelocityB + totalVelocityA * (massA - massB)) / (massA + massB);
 * finalTotalVelocityB = (2.0f * massA * totalVelocityA + totalVelocityB * (massB - massA)) / (massA + massB);
 * \endcode
 *
 * New velocities are set into the game objects with collision vector to opposite directions:
 * \code
 * gameObjectA->setVelX(-collisionVectorX * finalTotalVelocityA);
 * gameObjectA->setVelY(-collisionVectorY * finalTotalVelocityA);
 * gameObjectB->setVelX(+collisionVectorX * finalTotalVelocityB);
 * gameObjectB->setVelY(+collisionVectorY * finalTotalVelocityB);
 * \endcode
 *
 */
void GameEngine::handleBumpCollision(GameObject *gameObjectA, GameObject *gameObjectB) const
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!gameObjectA || !gameObjectB)
        qFatal("GameEngine::handleBumpCollision() : received NULL pointer(s).");

    if (!gameObjectA->isAlive() || !gameObjectB->isAlive())
        qFatal("GameEngine::handleBumpCollision() : received dead object(s).");

    bool error = false;

    if (gameObjectA->objectType() == TypeAsteroidLarge) {
        WrappingGameObject * wrappingGameObject = static_cast<WrappingGameObject *>(gameObjectA);
        if (wrappingGameObject->wrappingDisabled())
            error = true;
    }

    if (gameObjectB->objectType() == TypeAsteroidLarge) {
        WrappingGameObject * wrappingGameObject = static_cast<WrappingGameObject *>(gameObjectB);
        if (wrappingGameObject->wrappingDisabled())
            error = true;
    }

    if (error)
        qFatal("GameEngine::handleBumpCollision() : received object(s) with wrappingDisabled. "
            "changing direction of these objects might cause them to escape the screen. "
            "ObjectTypes are %d and %d", gameObjectA->objectType(), gameObjectB->objectType());

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // imitate inelastic collision (like two steel balls hitting together)
    // so that the momentums of the objects are exchanged, while the
    // sum of all velocities is maintained.

    // first compute the total velocities of the objects.

    FPtype totalVelocityA = sqrt(gameObjectA->velX() * gameObjectA->velX() + gameObjectA->velY() * gameObjectA->velY());
    FPtype totalVelocityB = sqrt(gameObjectB->velX() * gameObjectB->velX() + gameObjectB->velY() * gameObjectB->velY());

    // if both objects seem to be stopped (having zero total velocities)
    // the inelastic collision model fails -> just assign a minimal total velocity.

    if (totalVelocityA + totalVelocityB < 0.50f) {
        qWarning() << "GameEngine::handleBumpCollision() : small velocities " 
            << totalVelocityA << " " << totalVelocityB << " types " 
            << gameObjectA->objectType() << " " << gameObjectB->objectType();

        // sometimes asteroid objects appear to cluster in groups,
        // and we start getting a lot of messages here.

        // so, instead of just setting a minimal velocity here,
        // try giving a bigger punch so that those clusters would break up.

        totalVelocityA = 2.00f;
        totalVelocityB = 2.00f;
    }

    // compute collision vector and scale it to unit vector (length = 1).
    // the new velocity vectors are computed from collision vector so that
    // the objects start moving directly away from each other.

    FPtype collisionVectorX = gameObjectB->posX() - gameObjectA->posX();
    FPtype collisionVectorY = gameObjectB->posY() - gameObjectA->posY();

    FPtype collisionVectorLength = sqrt(collisionVectorX * collisionVectorX + collisionVectorY * collisionVectorY);

    // if the colliding objects have exactly the same positions,
    // cV_len will have a value of zero and we have a risk of
    // zero division later.

    if (collisionVectorLength < 0.10f) {
        qWarning() << "WARNING : GameEngine::handleBumpCollision() overlapping objects " 
            << collisionVectorLength;

        // randomize the collisionVectorX and collisionVectorY values and recompute
        // collisionVectorLength value accordingly -> the collision direction is randomized.

        collisionVectorX = getRandomF() - 0.50f;
        collisionVectorY = getRandomF() - 0.50f;

        collisionVectorLength = sqrt(collisionVectorX * collisionVectorX + collisionVectorY * collisionVectorY);
    }

    collisionVectorX /= collisionVectorLength;
    collisionVectorY /= collisionVectorLength;

    // add momentum impact on velocities
    // (based on conservation of momentum and conservation of kinetic energy).

    const FPtype massA = gameObjectA->mass();
    const FPtype massB = gameObjectB->mass();

    const FPtype finalTotalVelocityA = (2.0f * massB * totalVelocityB + totalVelocityA * (massA - massB)) / (massA + massB);
    const FPtype finalTotalVelocityB = (2.0f * massA * totalVelocityA + totalVelocityB * (massB - massA)) / (massA + massB);

    // write the new velocities into the objects.

    gameObjectA->setVelX(-collisionVectorX * finalTotalVelocityA);
    gameObjectA->setVelY(-collisionVectorY * finalTotalVelocityA);
    gameObjectA->checkSpeed();

    gameObjectB->setVelX(collisionVectorX * finalTotalVelocityB);
    gameObjectB->setVelY(collisionVectorY * finalTotalVelocityB);
    gameObjectB->checkSpeed();
}

/*! Handles a miner to asteroid collision.
 *
 * If miner is in ball mode, a collision handler for ''bump''-effect is called (\c
 * GameEngine::handleBumpCollision()) and the asteroid is split
 * (GameEngine::handleAsteroidSplitting()).
 *
 * If miner is in cone mode and is not protected a player crash handler is called (\c
 * handlePlayerCrash()). If player is protected and a hyper jump timer is not running a ''bump''
 * collision occurs.
 *
 * \param asteroidIndex The index of collided asteroid in an asteroid list.
 */
void GameEngine::handleMinerAsteroidCollision(int asteroidIndex)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (asteroidIndex < 0 || asteroidIndex >= asteroidList.count())
        qFatal("GameEngine::handleMinerAsteroidCollision() : bad list index.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    Asteroid *asteroid = asteroidList.at(asteroidIndex);

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!miner->isAlive())
        qFatal("GameEngine::handleBumpCollision() : miner is dead.");

    if (!asteroid->isAlive())
        qFatal("GameEngine::handleBumpCollision() : received dead asteroid.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (miner->mode() == SpaceCraftBallMode) {
        handleBumpCollision(miner, asteroid);
        handleAsteroidSplitting(asteroidIndex);
        if (soundEngine != NULL)
            newSoundEffect(SoundCollision);
    }
    else {

        // if player is protected, only BumpCollision
        // is needed here -> protection does not prevent
        // player from smashing asteroids.

        if (isPlayerProtected()) {

            // if the player is protected because a hyperjump
            // effect has been started, then skip also the
            // BumpCollision so that the animation is not
            // interfered (it just looks dummy if the ship
            // does not hit into the HyperJump "circle").

            bool enableBumpCollision = true;

            if (timerManager->time(GameTimer::hyperJumpOutTimer) >= 0)
                enableBumpCollision = false;

            if (timerManager->time(GameTimer::hyperJumpInTimer) >= 0)
                enableBumpCollision = false;

            if (enableBumpCollision)
                handleBumpCollision(miner, asteroid);
        }
        else {
            handlePlayerCrash(SoundExplosion);
        }
    }
}

/*! Handles a ''miner crashed''-event.
 *
 * \param type Sound effect type (one of the SoundEffectType enumeration values).
 */
void GameEngine::handlePlayerCrash(const SoundEffectType type)
{ 
    handlePlayerDies();
    createParticleExplosion(miner->posX(), miner->posY(), 30);
    if (soundEngine != NULL)
        newSoundEffect(type);
}

/*! Creates a particle explosion effect.
 * Particle effect is store into a ''particle buffer'' accessed with \c GameEngine::particleData().
 *
 * \param x X-coordinate location for particle effect.
 * \param y Y-coordinate location for particle effect.
 * \param particleCount Amout of particles rendered for the effect
 * \param particleVelocity Velocity of the particle movement
 */
void GameEngine::createParticleExplosion(FPtype x, FPtype y, int particleCount, FPtype particleVelocity)
{
    for (int i = 0;i < particleCount;i++) {
        GLfloat velX = (getRandomF() - 0.5f) * particleVelocity;
        GLfloat velY = (getRandomF() - 0.5f) * particleVelocity;
        particleData().addParticle(totalFrameCounter, totalTimeCounter, x, y, 0.0f, velX, velY, 0.50f, 4.0f);
    }
}

/*! Handles a ''miner died''-event.
 *
 * Miner is set into a ''dead'' state. The \c minersLeft count is decremented.
 * All the miner related effects currently running are terminated and the
 * ''Player crashed'' timer is started (\c GameTimer::statePlayerCrashedTimer).
 */
void GameEngine::handlePlayerDies()
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (isPlayerProtected()) {
        qFatal("GameEngine::handlePlayerDies() : spaceship died when it should be protected."
            "shipSafeTimer = %d"
            "hyperJumpOutTimer = %d"
            "hyperJumpInTimer = %d"
            "stateGetReadyTimer = %d"
            "stateLevelChangingTimer = %d", 
            timerManager->time(GameTimer::shipSafeTimer),
            timerManager->time(GameTimer::hyperJumpOutTimer),
            timerManager->time(GameTimer::hyperJumpInTimer),
            timerManager->time(GameTimer::stateGetReadyTimer),
            timerManager->time(GameTimer::stateLevelChangingTimer));
    }

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    miner->setDead(totalFrameCounter, totalTimeCounter);

    minersLeft--;

    // terminate all spaceship-related effects currently running.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // terminate HyperJump IN and OUT effects + cancel HyperJumpNavigation.

    timerManager->stopGameTimer(GameTimer::hyperJumpOutTimer);
    if (TimerManager::hyperJumpOutData != NULL) {
        delete TimerManager::hyperJumpOutData;
        TimerManager::hyperJumpOutData = NULL;
    }

    timerManager->stopGameTimer(GameTimer::hyperJumpInTimer);
    if (TimerManager::hyperJumpInData != NULL) {
        delete TimerManager::hyperJumpInData;
        TimerManager::hyperJumpInData = NULL;

        // because the hyperjump-in is cancelled,
        // we also need to cancel the navigation.

        if (hyperJumpNavigator != NULL) {
            delete hyperJumpNavigator;
            hyperJumpNavigator = NULL;
        }
    }

    if (hyperJumpNavigator != NULL) 
        qDebug() << "GameEngine::handlePlayerDies() : hyperJumpNavigator is left untouched.";

    timerManager->startGameTimer(GameTimer::statePlayerCrashedTimer);
}

/*! Replaces a large asteroid with medium sized asteroids
 * and a medium sized asteroid with mineral collectible. 
 *
 * When creating medium sized asteroids, the asteoids are appended into a ''collision group''.
 * Items in collision group are protected for collisions to each other for time defined in
 * \c GameObject::collisionFreeFrames. 
 *
 * Increments progress value and score.  Creates also randomly bomb collectibles if bomb 
 * collectibles are left for current level. The replaced object is set in ''dead'' state
 * waiting for possible rewind mode playback.
 *
 * \param asteroidIndex The index of collided asteroid in an asteroid list.
 */
void GameEngine::handleAsteroidSplitting(int asteroidIndex)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (asteroidIndex >= asteroidList.count())
        qFatal("Asteroid list index out of range!");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    currentLevelProgress[0] += ProgressOther;

    Asteroid *a = asteroidList.at(asteroidIndex);
    gameScore += a->scoreValue();

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!a->isAlive())
        qFatal("GameEngine::handleAsteroidSplitting() : asteroid is dead.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (a->objectType() == TypeAsteroidLarge) {
        Asteroid *newA = createAsteroid(TypeAsteroidMediumA, a);
        Asteroid *newB = createAsteroid(TypeAsteroidMediumB, a);
        Asteroid *newC = createAsteroid(TypeAsteroidMediumC, a);

        QList<GameObject *> collisionGroup;
        collisionGroup.append(newA);
        collisionGroup.append(newB);
        collisionGroup.append(newC);
        GameObject::setUnCollidable(collisionGroup);

        asteroidList.append(newA);
        asteroidList.append(newB);
        asteroidList.append(newC);
    } else {
        Collectible *newA = createCollectible(TypeCollectibleMineralA, a);
        Collectible *newB = createCollectible(TypeCollectibleMineralB, a);
        Collectible *newC = createCollectible(TypeCollectibleMineralC, a);
        Collectible *newD = createCollectible(TypeCollectibleMineralD, a);

        // no need to create a collisionGroup like above,
        // since collectible-collectible collisions are
        // not detected.

        collectibleList.append(newA);
        collectibleList.append(newB);
        collectibleList.append(newC);
        collectibleList.append(newD);
    }

    // check if it's time to create a bomb.

    if (levelData.bombs > 0 && getRandomF() < bombProbability) {
        levelData.bombs--;
        createBomb(a);
    }

    // mark the asteroid as dead so that it
    // will be removed from the list later.

    setObjectDead(a, totalFrameCounter, totalTimeCounter);
}

/*!
 * Handles a collision between the miner and a collectible. The collectible specific counter
 * is increased for bombs and hyper jump collectibles (hyper jumps are not currently
 * collectibles but could be easily enabled as ones). Collecting minerals
 * increase the \c currentLevelProgress counter:
 *
 * \code
 * switch (col->objectType()) {
 *    case TypeCollectibleMineralA:   // fall through
 *    case TypeCollectibleMineralB:   // fall through
 *    case TypeCollectibleMineralC:   // fall through
 *    case TypeCollectibleMineralD:
 *        currentLevelProgress[0] += ProgressOther;
 *        break;
 *    // ...
 * }
 * \endcode
 *
 * Sound effect is played, score is increased according to collectible's score value and 
 * the collectible object is set into ''dead'' state for possible rewind mode playback.
 *
 * \param collectibleIndex The index of collided collectible in an \c collectibleList.
 */
void GameEngine::handleMinerCollectibleCollision(int collectibleIndex)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (collectibleIndex >= collectibleList.count())
        qFatal("Collectible list index out of range!");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    Collectible *col = collectibleList[collectibleIndex];

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!col->isAlive()) 
        qFatal("GameEngine::handleMinerCollectibleCollision() : collectible is dead.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    switch (col->objectType()) {
        case TypeCollectibleMineralA:   // fall through
        case TypeCollectibleMineralB:   // fall through
        case TypeCollectibleMineralC:   // fall through
        case TypeCollectibleMineralD:
            currentLevelProgress[0] += ProgressOther;
            break;
        case TypeCollectibleBomb:
            bombCount++;
            break;
        case TypeCollectibleHyperJump:
            hyperJumpCount++;
            break;
        default:
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
            qFatal("Type %d is not supported collectible.", col->objectType());
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    break;
    }

    newSoundEffect(SoundCollect);

    gameScore += col->scoreValue();

    setObjectDead(col, totalFrameCounter, totalTimeCounter);
}

/*!
 * Handles misc game events which
 *
 * -# are not direct consequences of GameObject movement and collisions.
 * -# depend on game difficulty level and/or are of random nature.
 *
 * For example UFO shooting missiles:
 * \code
 * if (t == TypeUfo) {
 *     ...
 *     Ufo *u = static_cast<Ufo *>(go);
 *     fireMissile(u);
 *     ...
 * }
 * \endcode
 *
 * Makes UFO's to shoot missiles and guide the missiles. Missiles are shoot 
 * (\c GameEngine::fireMissile()) randomly depending on the \c GameLevelData::missileIncidence 
 * value for current level. Depending on the \c GameLevelData::missileGuidance the missile is
 * steered by calling \c GameEngine::steerMissile().
 *
 * This is method executed only if the game is in ''normal'' gameplay state - if none of the 
 * following game state timers are  running
 * - \c GameTimer::stateGetReadyTimer
 * - \c GameTimer::stateLevelChangingTimer
 * - \c GameTimer::stateGameOverTimer
 *
 * Updates the meteor shower radius values for collisin detection. Meteor shower radius expands.
 *
 * Calls \c GameEngine::moreGameObjects() to populate game level with more game objects.
 */
void GameEngine::controlGameEvents(FPtype /*timestep*/)
{
    // do here the game events which
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // 1) are not direct consequences of GameObject movement and collisions.
    // 2) depend on game difficulty level and/or are of random nature.

    // NOTE : if the game is in a state other than "normal game" state,
    // then this code is not run.

    if (timerManager->isActive(GameTimer::stateGetReadyTimer))
        return;

    if (timerManager->isActive(GameTimer::stateLevelChangingTimer))
        return;

    if (timerManager->isActive(GameTimer::stateGameOverTimer))
        return;

    // make the UFOs shoot missiles, and guide the missiles.
    // update the MeteorShower radius values for collision detection.

    for (int i = 0;i < enemyList.count();i++) {
        GameObject *go = enemyList.at(i);
        if (!go->isAlive()) continue;
        GameObjectType t = go->objectType();

        if (t == TypeUfo) {
            if (miner->isAlive() && getRandomF() < levelData.missileIncidence) {

                // let the UFO shoot now if it's clearly visible,
                // hold the fire otherwise...

                const GLfloat marginX = 0.85f * gameViewRangeX;
                const GLfloat marginY = 0.85f * gameViewRangeY;
                const GLfloat posX = go->posX();
                const GLfloat posY = go->posY();

                bool ufoVisible = true;

                if (posX < -marginX || posX > +marginX)
                    ufoVisible = false;

                if (posY < -marginY || posY > +marginY)
                    ufoVisible = false;

                if (ufoVisible) {
                    Ufo *u = static_cast<Ufo *>(go);
                    fireMissile(u);
                }
            }
        }

        if (t == TypeMissile) {
            if (levelData.missileGuidance > 0.0f) {
                Missile *m = static_cast<Missile *>(go);
                steerMissile(m);
            }
        }

        if (t == TypeMeteorShower) {
            MeteorShower *ms = static_cast<MeteorShower *>(go);
            ms->storeRadius(totalTimeCounter);
        }
    }

    // NOTE : building in TESTINGMODE causes the following
    // gamelevel / object management code to be skipped!!!

#ifndef SMASHMINER_CONFIG_ENABLE_TESTINGMODE

    moreGameObjects();

#else   // SMASHMINER_CONFIG_ENABLE_TESTINGMODE

    // if compiled with testing-mode then do testing initialization.
    // the method has a built-in check which makes sure that it is
    // not called repeatedly.

    testingModeInit();

#endif  // SMASHMINER_CONFIG_ENABLE_TESTINGMODE
}

/*! Cleans up expired game objects.
 * The \c GameEngine::isUnnecessary() checks if an object is not needed anymore for game play or
 * rewind mode playback. Expired asteroids, collectibles and enemies will be removed from object 
 * lists and deleted.
 *
 * Sets also off-screen (\c GameEngine::isOffScreen()) non-wrapping game objects (comets, meteor 
 * showers and black holes) to ''dead'' state to wait for rewind mode playback and delete them 
 * when they are not needed for rewind mode playback.
 */
void GameEngine::removeUnnecessaryObjects()
{
    for (int i = asteroidList.size() - 1;i >= 0;i--) {
        Asteroid *a = asteroidList[i];
        if (isUnnecessary(a)) {
            asteroidList.removeAt(i);
            delete a;
        }
    }

    for (int i = collectibleList.size() - 1;i >= 0;i--) {
        Collectible *c = collectibleList[i];
        if (isUnnecessary(c)) {
            collectibleList.removeAt(i);
            delete c;
        }
    }

    // set a margin +50% here (the margin for bad_position is also
    // +50% but it is only checked for wrapping objects which mean
    // large asteroids only in practice).

    // also see GameEngine::getStartParams() which must use SMALLER
    // margin than here (or else the newly added objects are
    // immediately removed here).

    FPtype margins[2] = { gameViewRangeX * 0.50f, gameViewRangeY * 0.50f };

    for (int i = enemyList.size() - 1; i >= 0; --i) {
        GameObject *go = enemyList[i];
        if (isUnnecessary(go)) {
            enemyList.removeAt(i);
            delete go;
        }
        else if (go->isAlive() && isOffScreen(go, margins)) {
            if (go->objectType() == TypeMeteorShower) {

                // for MeteorShowers, we need to do an extra check
                // before we can set the object dead.

                // for conveniency, MeteorShowers may be created
                // in long stretches, so that the tail objects may
                // become off-screen -> make sure that the object
                // has passed the centerpoint of it's trajectory
                // before setting it dead.

                MeteorShower *ms = static_cast<MeteorShower *>(go);
                if (ms->centerPointPassed())
                    setObjectDead(go, totalFrameCounter, totalTimeCounter);
            }
            else {
                setObjectDead(go, totalFrameCounter, totalTimeCounter);
            }
        }
    }

    if (blackhole != NULL) {
        if (isUnnecessary(blackhole)) {
            delete blackhole;
            blackhole = NULL;
        }
        else {
            if (blackhole->isAlive() && isOffScreen(blackhole, margins))
                setObjectDead(blackhole, totalFrameCounter, totalTimeCounter);
        }
    }

    if (comet != NULL) {
        if (isUnnecessary(comet)) {
            delete comet;
            comet = NULL;
        }
        else {
            if (comet->isAlive()) {

                // the comet margins must be extended by the comet length.

                FPtype cometMargins[2];
                FPtype cometLength = comet->getLength(totalTimeCounter);
                cometMargins[0] = margins[0] + cometLength;
                cometMargins[1] = margins[1] + cometLength;
                if (isOffScreen(comet, cometMargins))
                    setObjectDead(comet, totalFrameCounter, totalTimeCounter);
            }
        }
    }
}

/*!
 * Checks if given object is expired: it's no longer ''alive'' and it's no longer
 * needed for the rewind mode playback.
 *
 * To check if object is needed for the rewind mode playback, the difference from 
 * current time and the time object was set to ''dead'' state is compared to
 * #SMASHMINER_CONFIG_REWIND_PERIOD_SECONDS.
 *
 * \code
 * if (totalTimeCounter - gameObject->diedTime() > SMASHMINER_CONFIG_REWIND_PERIOD_SECONDS)
 *      return true;
 * \endcode
 *
 * \param gameObject Game object being checked if it's no longer needed.
 * \returns True if the given game object is no longer needed and can be cleaned up.
 */
bool GameEngine::isUnnecessary(GameObject *gameObject)
{
#ifndef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // just remove if not alive...

    if (gameObject->isAlive())
        return false;
    else
        return true;

#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    if (rewindEnabled) {

        // if the object is alive,
        // then it is not unnecessary.

        if (gameObject->isAlive())
            return false;

        // if a dead object is older than
        // the rewind period, then it is
        // unnecessary, not elsewhere.

        if (totalTimeCounter - gameObject->diedTime() > SMASHMINER_CONFIG_REWIND_PERIOD_SECONDS)
            return true;
        else
            return false;
    }
    else {

        // just remove if not alive...

        if (gameObject->isAlive())
            return false;
        else
            return true;
    }

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
}

/*!
 * Checks if the given game object's current location is off-screen.
 * The current location is compared to \c gameViewRangeX and 
 * \c gameViewRangeY with given margin settings.
 *
 * \param gameObject Game object being checked.
 * \param margins An array with x-margin value in index 0 and y-margin value in index 1.
 *
 * \returns True if game object is off-screen.
 */
bool GameEngine::isOffScreen(GameObject *gameObject, FPtype * margins)
{
    const FPtype posX = gameObject->posX();
    if (posX < -gameViewRangeX - margins[0]) return true;
    if (posX > +gameViewRangeX + margins[0]) return true;

    const FPtype posY = gameObject->posY();
    if (posY < -gameViewRangeY - margins[1]) return true;
    if (posY > +gameViewRangeY + margins[1]) return true;

    return false;
}

/*! Processes game effects stored in a list of new effects during the last game frame. 
 * During each game loop cycle the new effects are stored in the \c newEffects list.
 * In the end of game loop cycle the effects are played using \c GameEngine::processNewEffects().
 *
 * If rewind mode is enabled the played effects are queued into \c pastEffects queue for rewind
 * mode playback. Expired effects are cleaned up from the \c pastEffects queue.
 */
void GameEngine::processNewEffects()
{
    for (int i = 0;i < newEffects.size();i++)
        newEffects[i]->start();

#ifndef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // just delete the effects...

    for (int i = 0;i < newEffects.size();i++)
        delete newEffects[i];

#else   // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    if (rewindEnabled) {

        // check the pastEffects queue for removal
        // of obsolete effects.

        const FPtype timeLimit = SMASHMINER_CONFIG_REWIND_PERIOD_SECONDS + SMASHMINER_CONFIG_LONGEST_EFFECT_PERIOD;
        while (!pastEffects.empty() && totalTimeCounter - pastEffects.head()->timeStamp() > timeLimit) {
            GameEffect * effect = pastEffects.dequeue();
            delete effect;
        }

        // copy the effects into the pastEffects queue.

        for (int i = 0;i < newEffects.size();i++)
            pastEffects.enqueue(newEffects[i]);
    }
    else {

        // just delete the effects...

        for (int i = 0;i < newEffects.size();i++)
            delete newEffects[i];
    }

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // finally clear the newEffects list for the next frame.

    newEffects.clear();
}

/*! Integrates movement to a game object according to given time step.
 *
 * Game object's current location is appended with a product of game object's velocity, timestep 
 * and a scale factor. 
 * \code
 * gameObject->setPosX(gameObject->posX() + gameObject->velX() * timestep * x);
 * gameObject->setPosY(gameObject->posY() + gameObject->velY() * timestep * x);
 * \endcode
 *
 * The value x is an integration constant which determines how strongly the object velocities
 * change the object positions. 
 *
 * \param gameObject    A game object to integrate movement to
 * \param timestep      A timestep in milliseconds (calculated frame length).
 */
void GameEngine::integrateMotion(GameObject *gameObject, FPtype timestep)
{
    const FPtype x = 0.005f;

    // the value x here is an integration constant which determines
    // how strongly the object velocities change the object positions.

    // the value 0.005 is completely artifical, chosen just to get a
    // comfortable range for practical velocities (close to 1.0 and above).

    // also see:
    //^^^^^^^^^^^
    // GameEngine::shipHyperJumpOut()

    gameObject->setPosX(gameObject->posX() + gameObject->velX() * timestep * x);
    gameObject->setPosY(gameObject->posY() + gameObject->velY() * timestep * x);
}

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

void GameEngine::checkPosition(GameObject *gameObject, FPtype marginX, FPtype marginY)
{
    marginX += gameViewRangeX;
    marginY += gameViewRangeY;

    bool bad_position = false;

    if (gameObject->posX() < -marginX)
        bad_position = true;

    if (gameObject->posX() > +marginX)
        bad_position = true;

    if (gameObject->posY() < -marginY)
        bad_position = true;

    if (gameObject->posY() > +marginY)
        bad_position = true;

    if (bad_position) {
        qCritical() << "posX= " << gameObject->posX() << " marginX= " << marginX;
        qCritical() << "posY= " << gameObject->posY() << " marginY= " << marginY;
        qFatal("GameEngine::checkPosition() : bad_position : objectType=%d", gameObject->objectType());
    }
}

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

/*! Creates a Collectible instance of given type.
 *
 * The collectible receives it's location from the given ''host'' game object. Also the
 * velocity is calculated from the ''host'' objects velocity.
 *
 * \param type  One of the collectible types in enumeration ::GameObjectType (::TypeCollectibleBomb,
 *              ::TypeCollectibleHyperJump, ::TypeCollectibleMineralA, ::TypeCollectibleMineralB,
 *              ::TypeCollectibleMineralC or ::TypeCollectibleMineralD).
 * \param gameObject ''Host'' game object for collectible. 
 *
 * \returns A Collectible instance of given type.
 */
Collectible * GameEngine::createCollectible(GameObjectType type, const GameObject *gameObject)
{
    Collectible *collectible = 0;
    FPtype velocity = 3.0f;
    switch(type) {
        case TypeCollectibleBomb:
            collectible = new Bomb(totalFrameCounter, rewindEnabled, GameObjectRadiusBomb);
        break;
        case TypeCollectibleHyperJump:
            collectible = new HyperJump(totalFrameCounter, rewindEnabled,GameObjectRadiusHyperJump);
        break;
        case TypeCollectibleMineralA:
            collectible = new MineralA(totalFrameCounter, rewindEnabled);
        break;
        case TypeCollectibleMineralB:
            collectible = new MineralB(totalFrameCounter, rewindEnabled);
            velocity = 5.0f;
        break;
        case TypeCollectibleMineralC:
            collectible = new MineralC(totalFrameCounter, rewindEnabled);
            velocity = 8.0f;
        break;
        case TypeCollectibleMineralD:
            velocity = 10.0f;
            collectible = new MineralD(totalFrameCounter, rewindEnabled);
        break;
        default:
            qFatal("Type %d is not supported as collectible.", type);
    }

    // create random numbers at range [-1/2 , +1/2].
    FPtype randX = GameEngine::getRandomF() - 0.50f;
    FPtype randY = GameEngine::getRandomF() - 0.50f;

    if (gameObject) {
        collectible->setPosX(gameObject->posX());
        collectible->setPosY(gameObject->posY());
        collectible->setVelX((gameObject->velX() / velocity) + velocity * randX);
        collectible->setVelY((gameObject->velY() / velocity) + velocity * randY);
    }
    else {
        collectible->setPosX(0.0f);
        collectible->setPosY(0.0f);
        collectible->setVelX(velocity * randX);
        collectible->setVelY(velocity * randY);
    }

    return collectible;
}

/*! Creates an Asteroid instance of given type.
 *
 * Used for creating a large asteroid instance or a medium sized asteroid when splitting a
 * large asteroid.
 *
 * \param type  One of the collectible types in enumeration ::GameObjectType (::TypeAsteroidLarge,
 *              ::TypeAsteroidMediumA, ::TypeAsteroidMediumB, ::TypeAsteroidMediumC)
 * \param p     Optional ''parent'' asteroid, passed when splitting a large asteroid.
 *
 * \returns     An Asteroid instance of given type.
 */
Asteroid * GameEngine::createAsteroid(GameObjectType type, const Asteroid *p)
{
    GameObjectMass m = (type == TypeAsteroidLarge) ? MassLarge : MassMedium;
    Asteroid *a = new Asteroid(type, totalFrameCounter, rewindEnabled, m);
    if (p != NULL) {

        // this is big asteroid splitting event!
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // apply a translation to child asteroids so that
        // the big one appears to break down smoothly...

        GLfloat translationX=0.0f;
        GLfloat translationY=0.0f;
        GLfloat translationZ=0.0f;

        switch(a->objectType()) {
        case TypeAsteroidMediumA:
            translationX = asteroidMediumATranslationX;
            translationY = asteroidMediumATranslationY;
            translationZ = asteroidMediumATranslationZ;
            break;

        case TypeAsteroidMediumB:
            translationX = asteroidMediumBTranslationX;
            translationY = asteroidMediumBTranslationY;
            translationZ = asteroidMediumBTranslationZ;
            break;

        case TypeAsteroidMediumC:
            translationX = asteroidMediumCTranslationX;
            translationY = asteroidMediumCTranslationY;
            translationZ = asteroidMediumCTranslationZ;
            break;

        default:
            qFatal("ERROR : unknown asteroid type %d.", a->objectType());
        }

        // translate the translation vector according
        // to the rotation state of the big asteroid.

        QMatrix4x4 matrix;
        matrix.rotate(p->rotationAngle(), p->rotationAxis(0), p->rotationAxis(1), p->rotationAxis(2));

        const QVector4D translation1(translationX, translationY, translationZ, 1.0f);
        QVector4D translation2 = matrix.map(translation1);

        // translate the medium asteroids so that they
        // build up the original big asteroid.

        a->setPosX(p->posX() + translation2.x());
        a->setPosY(p->posY() + translation2.y());

        // set the Z coordinate using a specific method.
        // the Z coordinate is a constant, so we don't
        // need to worry about it in the rewind mode.

        a->setMediumAsteroidPosZ(translation2.z());

        // setup the velocities so that the asteroid
        // will break up smoothly. the value splitVelocity
        // is chosen so that
        // 1) the animation looks good, and
        // 2) the medium asteroids will fly apart soon enough
        // so that (usually) they no longer collide after
        // a special collision skipping test (counting some
        // frames after a splitting event) brings their
        // behaviour back to normal collision behaviour.
        // see GameObject::isCollidable() for the collision
        // skipping code.

        const FPtype splitVelocity = 2.0f;
        a->setVelX(p->velX() + translation2.x() * splitVelocity);
        a->setVelY(p->velY() + translation2.y() * splitVelocity);
        a->scaleSpeed(levelData.asteroidSpeedMedium);

        // copy the big asteroid rotation to medium ones.

        a->setRotationAngle(p->rotationAngle());

        const FPtype randomExtraRotation = GameEngine::getRandomF() * 0.10f;
        FPtype rotationSpeed = p->rotationSpeed() + randomExtraRotation;
        if (GameEngine::getRandomF() < 0.50f)
            rotationSpeed = -rotationSpeed;

        a->setRotationSpeed(rotationSpeed);

        const GLfloat first = p->rotationAxis(0);
        const GLfloat second = p->rotationAxis(1);
        const GLfloat third = p->rotationAxis(2);
        a->setRotationAxis(first, second, third);
    } else {

        // this is a new (large) asteroid creation event.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // the value 1.15 is a relative margin for computing
        // the (off-screen) position for the new asteroid.

        GameObjectStartParameters sp;
        getStartParams(AnyDirection, 1.15f, sp);

        a->setPosX(sp.startX);
        a->setPosY(sp.startY);

        a->setVelX(sp.unitVelX);
        a->setVelY(sp.unitVelY);
        a->scaleSpeed(levelData.asteroidSpeedLarge);

        // set wrapping disabled initially, until
        // the large asteroid appears to screen.

        // NOTE : we must carefully initialize the
        // position and velocity so that the asteroid
        // WILL appear entirely into the screen!!!

        a->setWrappingDisabled(true);
    }

    return a;
}

/*! Change the ship mode delayed using timer.
 * If either of the ship mode timers is running, then start an opposite timer.
 * Otherwise start the timer based on current ship mode.
 * Appends the effect to the list of new effects to be started at the end of 
 * current game cycle.
 */
void GameEngine::changeShipModeDelayed()
{
    GameEffect *ge = NULL;

    // if either of the shipmode timers is running, then start an opposite timer.
    // else start the timer based on current shipmode.

    if (timerManager->time(GameTimer::shipBall2ConeTimer) >= 0) 
        ge = new ConeToBallEffect(totalFrameCounter, totalTimeCounter, miner);
    else if (timerManager->time(GameTimer::shipCone2BallTimer) >= 0)
        ge = new BallToConeEffect(totalFrameCounter, totalTimeCounter, miner);
    else if (miner->mode() == SpaceCraftBallMode)
        ge = new BallToConeEffect(totalFrameCounter, totalTimeCounter, miner);
    else
        ge = new ConeToBallEffect(totalFrameCounter, totalTimeCounter, miner);

    newEffects.append(ge);
}

/*! Changes the ship mode instantly into give mode withoud animation delay.
 *
 * Stop the possibly running ship mode timer. Set the ship mode to give mode.
 * Request immidiate update on ship graphics by setting \c updateSpaceShipGraphics to true.
 *
 * \param newMode The ship mode for miner
 */
void GameEngine::setShipModeImmediate(SpaceCraftMode newMode)
{
    // first switch off the shipmode timers.

    timerManager->stopGameTimer(GameTimer::shipCone2BallTimer);
    timerManager->stopGameTimer(GameTimer::shipBall2ConeTimer);

    // then set the new shipmode.
    // and the ship status.

    miner->setMode(newMode);

    // finally make sure that the craft is painted in correct mode.

    if (newMode == SpaceCraftBallMode)
        miner->setTransformationStatus(1.0f);
    else
        miner->setTransformationStatus(0.0f);

    updateSpaceShipGraphics = true;
}

/*!
 * Returns the ship mode transformation status. The ship mode 
 * graphics are adjusted according to transformation status.
 * If the transformation status is more than 0.0 and less than 1.0
 * the ship is transforming from ball mode to cone mode or vice versa.
 *
 * \returns Ship mode transformation status (value from 0.0 to 1.0)
 */
GLfloat GameEngine::shipModeStatus() const
{
    return miner != NULL ? miner->transformationStatus() : 0.0f;
}

/*! Add steering effects for miner. Steering effects mimic space ship rocket effects using 
 * particle graphics.
 *
 * There are three ''levels'' for the effect. Each level has 
 * - a ''rocket level limit''
 * - an interval, how many milliseconds is passed between particle launches
 * - a particle velocity
 *
 * The effect level is determined according to steering value (direction and steepness) for both 
 * X- and Y-axels (\c minerSteeringX and \c minerSteeringY). 
 *
 * Particle effects are appended into ''particle buffer''.
 *
 * \param timeStepAsInt Time step calculated for current game cycle as an integer value. 
 */
void GameEngine::addSpaceShipSteeringEffects(int timeStepAsInt)
{
    int newlevel;
    FPtype steering;

    // rocketLevelLimits are chosen at range [0,+1]
    // to divide the effect in three "levels".

    static const GLfloat rocketLevelLimit1 = 0.025;
    static const GLfloat rocketLevelLimit2 = 0.100;
    static const GLfloat rocketLevelLimit3 = 0.500;

    // "intervals" tell how many milliseconds
    // is passed between new particle launches
    // at each effect "level".

    static int intervals[3] = {
        60, 40, 20
    };

    // "velocities" tell the particle velocities
    // at each effect "level", and there is also
    // a random velocity component which is added
    // to the directional velocity.

    static GLfloat velocities[3] = {
        0.060, 0.075, 0.090
    };

    static const GLfloat randomVelocity = 0.050f;

    bool levelChangedX = false;
    bool levelChangedY = false;

    GLfloat posX;
    GLfloat posY;

    GLfloat velX;
    GLfloat velY;

    GLfloat direction;
    const GLfloat shift = miner->objectRadius();

    const GLfloat posZ = 0.0f;

    const GLfloat pFrict = 1.00f;
    const GLfloat pSize = 3.0f;

    // add x-effects.
    //^^^^^^^^^^^^^^^^

    steering = fabs(minerSteeringX);
    if (steering < rocketLevelLimit2) {
        if (steering < rocketLevelLimit1)
            newlevel = 0;
        else
            newlevel = 1;
    }
    else {
        if (steering < rocketLevelLimit3)
            newlevel = 2;
        else
            newlevel = 3;
    }

    if (newlevel != minerRocketLevelX)
        levelChangedX = true;
    minerRocketLevelX = newlevel;

    if (newlevel > 0) {
        minerRocketCounterX += timeStepAsInt;
        if (minerRocketCounterX >= intervals[newlevel - 1]) {

            // now send one or more particles...

            direction = (minerSteeringX < 0.0f ? +1.0f : -1.0f);

            posX = miner->posX() + direction * shift;
            posY = miner->posY();

            while (minerRocketCounterX >= intervals[newlevel - 1]) {
                minerRocketCounterX -= intervals[newlevel - 1];

                velX = direction * velocities[newlevel - 1] + (getRandomF() - 0.5f) * randomVelocity;
                velY = (getRandomF() - 0.5f) * randomVelocity;

                particleData().addParticle(totalFrameCounter, totalTimeCounter, posX, posY, posZ, velX, velY, pFrict, pSize);
            }
        }
    }

    // add y-effects.

    steering = fabs(minerSteeringY);
    if (steering < rocketLevelLimit2) {
        if (steering < rocketLevelLimit1)
            newlevel = 0;
        else
            newlevel = 1;
    }
    else {
        if (steering < rocketLevelLimit3)
            newlevel = 2;
        else
            newlevel = 3;
    }

    if (newlevel != minerRocketLevelY)
        levelChangedY = true;
    minerRocketLevelY = newlevel;

    if (newlevel > 0) {
        minerRocketCounterY += timeStepAsInt;
        if (minerRocketCounterY >= intervals[newlevel - 1]) {

            // now send one or more particles...

            direction = (minerSteeringY < 0.0f ? +1.0f : -1.0f);

            posX = miner->posX();
            posY = miner->posY() + direction * shift;

            while (minerRocketCounterY >= intervals[newlevel - 1]) {
                minerRocketCounterY -= intervals[newlevel - 1];

                velX = (getRandomF() - 0.5f) * randomVelocity;
                velY = direction * velocities[newlevel - 1] + (getRandomF() - 0.5f) * randomVelocity;

                particleData().addParticle(totalFrameCounter, totalTimeCounter, posX, posY, posZ, velX, velY, pFrict, pSize);
            }
        }
    }

    // particle effects added.
    //^^^^^^^^^^^^^^^^^^^^^^^^^

    // NOTE : also spaceship sound effects could be added
    // and monitored based on minerRocketLevelX and minerRocketLevelY.
}

/*! Add particle effects on missiles.
 *
 * Missiles are collected into a vector from enemyList. 
 * Amount of particles is determined using the time step for current game loop cycle.
 * Particle effects are appended into ''particle buffer'' accessed using
 * \c GameEngine::particleData().
 *
 * \param timeStepAsInt Time step calculated for current game cycle as an integer value. 
 */
void GameEngine::addMissileParticleEffects(int timeStepAsInt)
{
    // find the missiles from enemyList.

    QVector<Missile *> missiles;

    for (int i = 0;i < enemyList.count();i++) {
        GameObject * go = enemyList.at(i);

        if (!go->isAlive())
            continue;

        if (go->objectType() == TypeMissile)
            missiles.append(static_cast<Missile *>(go));
    }

    if (!missiles.count())
        return;

    // determine how many particles to add.

    static int missileParticleCounter = 0;
    const int missileParticleInterval = 25;

    int particleCount = 0;

    missileParticleCounter += timeStepAsInt;
    while (missileParticleCounter >= missileParticleInterval) {
        missileParticleCounter -= missileParticleInterval;
        particleCount++;
    }

    if (!particleCount)
        return;

    // then add the particles for each of the missiles.

    const GLfloat posZ = 0.0f;

    const GLfloat pFrict = 1.00f;
    const GLfloat pSize = 3.0f;

    for (int mIndex = 0;mIndex < missiles.count();mIndex++) {
        Missile * m = missiles.at(mIndex);

        const GLfloat posX = m->posX();
        const GLfloat posY = m->posY();

        for (int i = 0;i < particleCount;i++) {
            const GLfloat randomVelocity = 0.050f;
            const GLfloat velX = (getRandomF() - 0.5f) * randomVelocity;
            const GLfloat velY = (getRandomF() - 0.5f) * randomVelocity;

            particleData().addParticle(totalFrameCounter, totalTimeCounter, posX, posY, posZ, velX, velY, pFrict, pSize);
        }
    }
}

/*! Add particle effects on comet.
 *
 * Amount of particles is determined using the time step for current game loop cycle.
 * Particle effects are appended into ''particle buffer'' accessed using
 * \c GameEngine::particleData().
 *
 * \param timeStepAsInt Time step calculated for current game cycle as an integer value. 
 */
void GameEngine::addCometParticleEffects(int timeStepAsInt)
{
    // determine how many particles to add.

    static int cometParticleCounter = 0;
    const int cometParticleInterval = 10;

    // NOTE-1 : interval 10 may exhaust particlebuffer of size 2000?

    // NOTE-2 : ideally the interval should get smaller when the comet
    // size (product of length and width) increases - but how to handle
    // this so that that the particlebuffer is never exhausted???

    int particleCount = 0;

    cometParticleCounter += timeStepAsInt;
    while (cometParticleCounter >= cometParticleInterval) {
        cometParticleCounter -= cometParticleInterval;
        particleCount++;
    }

    if (!particleCount)
        return;

    // then add the particles.

    const GLfloat direction = comet->rotationAngle() + M_PI;
    const GLfloat length = comet->getLength(totalTimeCounter);
    const GLfloat width = comet->getWidth(totalTimeCounter);

    const GLfloat posZ = 0.0f;

    const GLfloat pFrict = 1.00f;
    const GLfloat pSize = 3.0f;

    for (int i = 0;i < particleCount;i++) {

        // compute individual particle starting parameters.
        // a comet speed value of 4.0 was used when adjusting
        // the parameters below (comet should not go slower).

        // "target" is a visual position of the particle along
        // the length of the comet, scaled from 0.0 to 1.0

        const GLfloat target = getRandomF();

        // compute the particle starting position along the comet
        // tail (the "shift" value) which ranges from 0.0 to 0.80

        const GLfloat shift = target * 0.80f;
        const GLfloat posX = comet->posX() + sin(direction) * shift * length;
        const GLfloat posY = comet->posY() + cos(direction) * shift * length;

        // give the particles nearest to the tail a directed
        // velocity (in direction of the tail), smoothing the edge.

        const GLfloat directedStart = 0.25f;
        GLfloat tmpf = shift - directedStart;
        if (tmpf < 0.0f)
            tmpf = 0.0f;

        // the value 0.0015 was adjusted by setting the other
        // components of computed particle velocity to zero.

        // NOTE : the comet velocity also affects this (extending
        // the particle stream) but the effect is not considered here.

        const GLfloat directedVelocity = tmpf * tmpf * length * 0.0015f;
        const GLfloat velX = sin(direction) * directedVelocity;
        const GLfloat velY = cos(direction) * directedVelocity;

        GLfloat randomVelX = velX;
        GLfloat randomVelY = velY;

        // the constant 0.0025 here is experimental, and it
        // is adjusted to show the correct comet tail width.

        const GLfloat transverse = target * width * 0.0025f;
        const GLfloat randomTransverse = (getRandomF() - 0.5f) * transverse;
        randomVelX -= cos(direction) * randomTransverse;  // a negative term!
        randomVelY += sin(direction) * randomTransverse;

        // the constant 0.050 here is experimental, and it is
        // adjusted for suitable "randomness" of comet particles.

        const GLfloat fullyRandom = 0.050f;
        randomVelX += (getRandomF() - 0.5f) * fullyRandom;
        randomVelY += (getRandomF() - 0.5f) * fullyRandom;

        particleData().addParticle(totalFrameCounter, totalTimeCounter, posX, posY,
                                   posZ, randomVelX, randomVelY, pFrict, pSize);
    }
}

/*! Add particle effects on black hole.
 *
 * Amount of particles is depends of the time step for current game loop cycle.
 * Particle effects are appended into ''particle buffer'' accessed using
 * \c GameEngine::particleData().
 *
 * \param timeStepAsInt Time step calculated for current game cycle as an integer value. 
 */
void GameEngine::addBlackHoleParticles(int timeStepAsInt)
{
    static const int bhParticleInterval = 25;
    static int bhParticleClock = 0;
    bhParticleClock += timeStepAsInt;

    if (bhParticleClock >= bhParticleInterval) {

        // now send one or more particles...

        const GLfloat pSize = 3.0f;

        while (bhParticleClock >= bhParticleInterval) {
            bhParticleClock -= bhParticleInterval;

            // compute a random distance at range 1 <-> 11
            GLfloat dist = (0.1f + getRandomF()) * 10.0f;

            // compute a random angle (in radians) at range 0 <-> 2pi
            GLfloat angle = getRandomF() * 2.0f * M_PI;

            // compute a random velocity value at range 0.01 <-> 0.02
            GLfloat radvel = (1.0f + getRandomF()) * 0.01;

            particleData().addParticleBlackHole(totalFrameCounter, totalTimeCounter, dist, angle, radvel, pSize);
        }
    }
}

/*! Add hyper jump particle effects (circle of points).
 *
 * Amount of particles is depends of the time step for current game loop cycle.
 * Computes a set of fixed angles (full circle divided to parts) creating a circle of points.
 *
 * Particle effects are appended into ''particle buffer'' accessed using
 * \c GameEngine::particleData().
 *
 * \param timeStepAsInt Time step calculated for current game cycle as an integer value. 
 */
void GameEngine::addHyperJumpParticles(int timeStepAsInt)
{
    static const int hjParticleInterval = 50;
    static int hjParticleClock = 0;
    hjParticleClock += timeStepAsInt;

    static const int hjParticleSteps = 6;   // hexagon...
    static int hjPatricleStep = 0;

    if (hjParticleClock >= hjParticleInterval) {

        // now send one or more particles...

        const GLfloat pSize = 4.0f;

        while (hjParticleClock >= hjParticleInterval) {
            hjParticleClock -= hjParticleInterval;

            if (++hjPatricleStep >= hjParticleSteps)
                hjPatricleStep = 0;

            // compute a set of fixed angles (full circle divided to parts
            // as determined by hjParticleSteps) creating a circle of points.
            const GLfloat angle = hjPatricleStep * 2.0f * M_PI / hjParticleSteps;

            if (TimerManager::hyperJumpOutData != NULL)
                particleData().addParticleHyperJump(totalFrameCounter, totalTimeCounter, angle, pSize, 0);

            if (TimerManager::hyperJumpInData != NULL)
                particleData().addParticleHyperJump(totalFrameCounter, totalTimeCounter, angle, pSize, 1);
        }
    }
}

/*! Appends a black hole gravity effect on game object. If game object is close enough
 * to black hole center it gets sucked in. 
 * First a \c BlackHoleLethalDistance value is calculated using black hole radius and game object
 * radius:
 * \code
 * BlackHoleLethalDistance = blackhole->objectRadius() - gameObject->objectRadius() * 0.75f;
 * \endcode
 *
 * A vector from game object to black hole is calculated.
 * Distance between black hole and game object is calculated from vector using Pythagorean theorem
 * \f$c=\sqrt{a^2+b^2}\f$:
 * \code
 * gameObjectToBlackHoleX = blackHoleX - gameObjectX;
 * gameObjectToBlackHoleY = blackHoleY - gameObjectY;
 * gameObjectToBlackHoleDistance = sqrt(gameObjectToBlackHoleX * gameObjectToBlackHoleX 
 *          + gameObjectToBlackHoleY * gameObjectToBlackHoleY);
 * \endcode
 *
 * If game object is inside the black hole lethal distance (\c BlackHoleLethalDistance) the game 
 * object is set into ''dead'' state.
 *
 * If game object is inside the black hole effect distance defined in \c
 * GameEngine::BlackHoleEffectDistance the black hole effect gravity effect is appended.
 *
 * The vector from game object to black hole is scaled into an unit vector.
 *
 * \code
 * gameObjectToBlackHoleX /= gameObjectToBlackHoleDistance;
 * gameObjectToBlackHoleY /= gameObjectToBlackHoleDistance;
 * \endcode
 *
 * A black hole gravity impact factor from values 0.0 to 1.0 is calculated. The impact is near
 * zero when the distance is great and it goes near 1 when game object is near black hole.
 * \code
 * blackHoleFactor = 1.0 - ((gameObjectToBlackHoleDistance - BlackHoleLethalDistance)
 *      / (BlackHoleEffectDistance - BlackHoleLethalDistance));
 * \endcode
 *
 * The impact factor is adjusted according to game object mass. The larger the mass the less the
 * gravity impact. 
 *
 * The acceleration formula towards the black hole is \f$abx^2\f$ where 
 * - a is a black hole force factor defined in \c GameEngine::BlackHoleForce:
 * - b is the object's mass factor (\c massFactor) and
 * - x is the black hole gravity impact factor (\c blackHoleFactor)
 *
 * \code
 * acceleration = BlackHoleForce * massFactor * blackHoleFactor * blackHoleFactor;
 * \endcode
 *
 * The direction vector is scaled into a gravity vector.
 * \code
 * gameObjectToBlackHoleX *= acceleration;
 * gameObjectToBlackHoleY *= acceleration;
 * \endcode
 *
 * Finally the game object's velocity vector is adjusted using the black hole gravity vector. 
 * \code
 * gameObject->setVelX(gameObject->velX() + gameObjectToBlackHoleX);
 * gameObject->setVelY(gameObject->velY() + gameObjectToBlackHoleY);
 * \endcode
 *
 */
void GameEngine::doBlackHoleEffect(GameObject *gameObject)
{
    // adjust BlackHoleLethalDistance so that all objects seem to "fall in"
    // to a blackhole, no matter what their size is : the radius is the blackhole
    // radius minus the object radius. compromise a bit (75%) from full object
    // radius so that blackhole lethal effect would be as probable as possible.

    // the blackhole radius should be bigger than any other object radius for
    // lethal effect to remain functional - therefore reserve lethal radius
    // value of 5.0 for all situations.

    GLfloat BlackHoleLethalDistance = blackhole->objectRadius() - gameObject->objectRadius() * 0.75f;
    if (BlackHoleLethalDistance < 5.0f)
        BlackHoleLethalDistance = 5.0f;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (blackhole == NULL || gameObject == NULL || !gameObject->isAlive())
        qFatal("GameEngine::doBlackHoleEffect() : invalid call.");

    if (BlackHoleEffectDistance <= 0)
        qFatal("BlackHoleEffectDistance value must be greater than zero!");

    if (BlackHoleLethalDistance >= BlackHoleEffectDistance)
        qFatal("BlackHoleLethalDistance must be less than BlackHoleEffectDistance!");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // black hole location

    const FPtype blackHoleX = blackhole->posX();
    const FPtype blackHoleY = blackhole->posY();

    // game object location

    const FPtype gameObjectX = gameObject->posX();
    const FPtype gameObjectY = gameObject->posY();

    // Adjust the game object's velocity vector towards the black hole
    // depending on the distance of the black hole and the weight of
    // the game object.
    
    // calculate a vector between game object and black hole

    FPtype gameObjectToBlackHoleX = blackHoleX - gameObjectX;
    FPtype gameObjectToBlackHoleY = blackHoleY - gameObjectY;

    // calculate the distance between game object and black hole

    FPtype gameObjectToBlackHoleDistance = sqrt(gameObjectToBlackHoleX * gameObjectToBlackHoleX 
            + gameObjectToBlackHoleY * gameObjectToBlackHoleY);

    if (gameObjectToBlackHoleDistance > BlackHoleEffectDistance)
        return;

    if (gameObjectToBlackHoleDistance <= BlackHoleLethalDistance) {
        setObjectDead(gameObject, totalFrameCounter, totalTimeCounter);
        return;
    }

    // scale the vector between game object and black hole to unit a vector
    // NOTE : gameObjectToBlackHoleDistance cannot be zero (since we had returned then).
    gameObjectToBlackHoleX /= gameObjectToBlackHoleDistance;
    gameObjectToBlackHoleY /= gameObjectToBlackHoleDistance; 

    // Create a black hole gravity impact factor [0.0 ... 1.0]
    //
    // The impact is near zero when the distance is great and 
    // it goes near 1 when game object is near black hole.
    const float blackHoleFactor = 1.0 - ((gameObjectToBlackHoleDistance - BlackHoleLethalDistance) 
            / (BlackHoleEffectDistance - BlackHoleLethalDistance));

    // Adjust the blackHoleFactor according to the mass of an object
    // The larger the mass the less the gravity impact.

    const float mass = gameObject->mass();

    // The spaceship will get more gravitation punishment since player can react to 
    // gravitation and steer the spaceship away.
    float massFactor = gameObject->objectType() == TypeSpaceCraft
        ? 1.0f / (mass / 4.0f)
        : 1.0f / mass;

    // the acceleration formula is currently abx^2:
    float acceleration = BlackHoleForce * massFactor * blackHoleFactor * blackHoleFactor;

    // scale the unit vector to a gravity vector 
    gameObjectToBlackHoleX *= acceleration;
    gameObjectToBlackHoleY *= acceleration;

    // adjust the game object's velocity vector with black hole gravity vector
    gameObject->setVelX(gameObject->velX() + gameObjectToBlackHoleX);
    gameObject->setVelY(gameObject->velY() + gameObjectToBlackHoleY);

    gameObject->checkSpeed();
}

/*! Resets the miner to given location.
 * The ship mode is set to ''Ball'' mode and the member variables are reset.
 *
 * \param positionX X-coordinate for miner location.
 * \param positionY Y-coordinate for miner location.
 */
void GameEngine::resetSpaceShip(FPtype positionX, FPtype positionY)
{
    qDebug() << "GameEngine::resetSpaceShip() at frame " << totalFrameCounter
            << " to x: " << positionX << " y: " << positionY;

    // reset the spaceship object members.
    // also see GameObject constructor for the settings.

    miner->storedDiedFrame = -1;
    miner->storedDiedTime = -999.9;

    miner->setPosX(positionX);
    miner->setPosY(positionY);

    miner->setVelX(0.0);
    miner->setVelY(0.0);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // reset the spaceship rewind storage as well.

    miner->writeIndex = 0;
    miner->hasWrapped = 0;
    miner->readIndex = 0;

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    setShipModeImmediate(SpaceCraftBallMode);

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    // when we reset the spaceship's alive/dead
    // status, it means that we can't guarantee
    // a complete rewind playback anymore.

    // -> clear the the entire rewind history,
    // except the latest (current) frame.

    cleanUpRewindFrames(false);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE
}

/*! 
 * Creates a hyper jump-out effect for the miner space ship. Hyper jump-out effect
 * is used for exiting miner from screen to hyper jump. Starts immediately looking for a hyper 
 * jump-in place.  Hyper jump-in effect is started at halftime of hyperjump-out timer 
 * (\c GameTimer::hyperJumpOutTimer). 
 *
 * A hyper jump time is calculated. Hyper jump time is the time (in seconds) when
 * -# the hyper jump-out effect is shown (to hide the player)
 * -# the hyper jump-in effect is started (to bring the player back)
 *
 * Hyper jump time is half of the \c GameTimer::hyperJumpOutTimer time (converted to seconds) 
 * since the hyper jump-in effect will be started at half-time of the \c hyperJumpOutTimer and
 * starting \c HyperJumpOutEffect will start the timer.
 *
 * A new HyperJumpNavigator is instantiated to calculate a new location for the miner when
 * coming out of hyper jump. The hyper jump time is used for calculating the hyper jump-out time.
 *
 * A location for the hyper jump-out effect is calculated based on current location of the miner. 
 * Hyper jump time is used to calculate the miner location when hyper jump-out effect occurs. If 
 * the location is out of range the location will be wrapped to the other side of screen.
 *
 * Finally a new \c HyperJumpOutEffect is instantiated (with calculated target location) and 
 * appended to the list of new effects.
 */
void GameEngine::shipHyperJumpOut()
{
    // start immediately looking for a new HyperJump-in place!!!
    // hyperjump-in effect is started at halftime of hyper jump-out timer.

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (hyperJumpNavigator != NULL)
        qFatal("GameEngine::shipHyperJumpOut() : hyperJumpNavigator != NULL");

    if (TimerManager::hyperJumpOutData != NULL)
        qFatal("GameEngine::shipHyperJumpOut() : hyperJumpOutData != NULL");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // hyperJumpTime is the time (in seconds) when
    // 1) the hyperJumpOut effect is shown (to hide the player)
    // 2) the hyperJumpIn effect is started (to bring the player back)

    // it is computed by converting the timer length (in milliseconds) to
    // seconds and dividing the result further by 2 since the HyperJumpIn
    // effect will be started at half-time of the hyperJumpOut timer and
    // starting HyperJumpOutEffect will start the timer (see below).

    FPtype hyperJumpTime = (FPtype) timerManager->timerLength(GameTimer::hyperJumpOutTimer) / 2000.0f;

    hyperJumpNavigator = new HyperJumpNavigator(totalTimeCounter + hyperJumpTime, gameViewRangeX, gameViewRangeY);

    // compute the hyperjump out location.
    // the integration constant 5.0 here is the same integration constant
    // as used in GameEngine::integrateMotion() BUT it is first converted
    // from a millisecond-based value 0.005 to a second-based value by
    // multiplicating with 1000.

    GLfloat outX = miner->posX();
    GLfloat outY = miner->posY();

    // This will calculate the target X-position of the object in the future.
    outX += miner->velX() * hyperJumpTime * 5.0f;

    // apply wrapping to the result if needed,
    if (outX > +gameViewRangeX)
        outX -= 2.0f * gameViewRangeX;
    if (outX < -gameViewRangeX)
        outX += 2.0f * gameViewRangeX;

    // This will calculate the target Y-position of the object in the future.
    outY += miner->velY() * hyperJumpTime * 5.0f;

    // apply wrapping to the result if needed,
    if (outY > +gameViewRangeY)
        outY -= 2.0f * gameViewRangeY;
    if (outY < -gameViewRangeY)
        outY += 2.0f * gameViewRangeY;

    GameEffect *ge = new HyperJumpOutEffect(totalFrameCounter, totalTimeCounter, outX, outY);
    newEffects.append(ge);
}

/*! 
 * Creates a hyper jump-in effect for miner space ship. Hyper jump-in effect is used
 * to bring the miner back to screen from hyper jump. \c HyperJumpNavigator instance and 
 * \c TimerManager::hyperJumpInData should be available --- if not, a recovery will be made and 
 * method is leaved. Gets a location for miner and \c HyperJumpInEffect from the 
 * \c HyperJumpNavigator instance. A new \c HyperJumpInEffect effect instance is created and 
 * appended to the list of new effects (\c newEffects). A reset can be requested for the miner 
 * space ship (\c GameEngine::resetSpaceShip()).
 *
 * \param needSpaceShipReset Set to true to request a reset for the miner space ship.
 */
void GameEngine::shipHyperJumpIn(bool needSpaceShipReset)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    qDebug() << "HYPERJUMP-IN lauched with needSpaceShipReset = " << needSpaceShipReset;
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    bool looksBadButTryToRecover = false;

    if (hyperJumpNavigator == NULL) {
        qWarning() << "GameEngine::shipHyperJumpIn() : no hJN at hyperJumpInTimer.";
        looksBadButTryToRecover = true;
    }

    if (TimerManager::hyperJumpInData != NULL) {
        qWarning() << "GameEngine::shipHyperJumpIn() : hyperJumpInData already exists at hyperJumpInTimer.";
        looksBadButTryToRecover = true;
    }

    if (looksBadButTryToRecover) {

        // now we are in a severe error condition,
        // but try to recover and keep the show going on.

        // the preparations for a HyperJumpIn effect for
        // bringing the player to the screen have failed,
        // and we need to just pop the player to the
        // screen without any effects.

        // clean all the HyperJump effect timers and data.

        timerManager->stopGameTimer(GameTimer::hyperJumpInTimer);
        if (TimerManager::hyperJumpInData != NULL) {
            delete TimerManager::hyperJumpInData;
            TimerManager::hyperJumpInData = NULL;
        }

        timerManager->stopGameTimer(GameTimer::hyperJumpOutTimer);
        if (TimerManager::hyperJumpOutData != NULL) {
            delete TimerManager::hyperJumpOutData;
            TimerManager::hyperJumpOutData = NULL;
        }

        return;     // then return WITHOUT starting any effects!
    }

    HyperJumpDestination hyperJumpDestination;
    hyperJumpNavigator->getBest(hyperJumpDestination);

    const GLfloat inX = hyperJumpDestination.posX;
    const GLfloat inY = hyperJumpDestination.posY;

    delete hyperJumpNavigator;
    hyperJumpNavigator = NULL;

    if (needSpaceShipReset) {
        resetSpaceShip(inX, inY);
    }
    else {
        miner->setPosX(inX);
        miner->setPosY(inY);
    }

    GameEffect *ge = new HyperJumpInEffect(totalFrameCounter, totalTimeCounter, inX, inY);
    newEffects.append(ge);
}

/*!
 * Creates a bomb effect starting from given location.
 * Creates a new \c BombEffect instance to give location and appends it to the list of new
 * effects. Bomb count is decremented and a sound effect will also be appended.
 *
 * \param posX X-coordinate of the bomb effect.
 * \param posY Y-coordinate of the bomb effect.
 */
void GameEngine::launchBomb(FPtype posX, FPtype posY)
{
    if (!miner->isAlive() || bombCount <= 0 
        || timerManager->isActive(GameTimer::stateGetReadyTimer)
        || timerManager->isActive(GameTimer::stateLevelChangingTimer)) {
        return;
    }

    bool effectAlreadyExists = false;
    if (TimerManager::bombEffectData != NULL)
        effectAlreadyExists = true;

    if (effectAlreadyExists)
        return;

    GameEffect *ge = new BombEffect(totalFrameCounter, totalTimeCounter, posX, posY);
    newEffects.append(ge);

    bombCount--;

    if (soundEngine != NULL)
        newSoundEffect(SoundBlast);
}

/*!
 * Creates a bomb self explosion effect. If bomb is not collected during it's 
 * lifecycle it will explode. If miner is too close to explosion it will
 * also explode.
 *
 * A particle explosion effect is created (\c GameEngine::createParticleExplosion()).
 * The distance between miner and bomb location is calculated and if the
 * distace is under the set deadly distance the player will crash 
 * (\c GameEngine::handlePlayerCrash()).
 *
 * \param posX The bomb X-location.
 * \param posY The bomb Y-location.
 */
void GameEngine::launchBombSelf(FPtype posX, FPtype posY)
{
    createParticleExplosion(posX, posY, 100);

    if (isPlayerProtected())
        return;

    // the bomb's deadly distace to player is (50.0)^2 = 2500.0

    const FPtype distX = miner->posX() - posX;
    const FPtype distY = miner->posY() - posY;
    const FPtype distSqr = distX * distX + distY * distY;
    if (distSqr < 2500.0f)
        handlePlayerCrash(SoundBlast);
}

void GameEngine::doResizeRecovery(int oldWidth, int oldHeight)
{
    const FPtype scaleX = (FPtype) screenWidth() / (FPtype) oldWidth;
    const FPtype scaleY = (FPtype) screenHeight() / (FPtype) oldHeight;

    // go through all GameObjects and scale their positions
    // according to the changes in view_range_x and view_range_y.

    GameObject * o;
    FPtype posX;
    FPtype posY;

    if (miner) {
        o = miner;
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    for (int i = 0;i < asteroidList.count();i++) {
        o = asteroidList.at(i);
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    for (int i = 0;i < collectibleList.count();i++) {
        o = collectibleList.at(i);
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    for (int i = 0;i < enemyList.count();i++) {
        o = enemyList.at(i);
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    if (blackhole != NULL) {
        o = blackhole;
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    if (comet != NULL) {
        o = comet;
        posX = o->posX() * scaleX;
        posY = o->posY() * scaleY;
        o->setPosX(posX);
        o->setPosY(posY);
    }

    // note : HyperJumpNavigator has following members:
    // FPtype gameViewRangeX; FPtype gameViewRangeY and three HyperJumpDestinations;
    // those should be updated as well. however, resize situation when in hyperjump is a very rare
    // condition and it can only slightly affect HyperJumpDestination.
    // Currently this condition is ignored.
}

/*!
 * Creates a new sound effect of given type and appends it to the list of new effects.
 * \param effectType One of the sound effect types defined with enumeration \c ::SoundEffectType.
 */
void GameEngine::newSoundEffect(SoundEffectType effectType)
{
    if (effectType != SoundNull) {
        SoundEffect *sound = soundEngine->createSoundEffect(effectType, totalFrameCounter,
                                                            totalTimeCounter);
        GameEffect *effect = qobject_cast<GameEffect *>(sound);
        newEffects.append(effect);
    }
}

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

/*!
 * Stores the game object positions for the rewind mode playback.
 * The current location is stored for each game object using 
 * \c GameObject::writePosition().
 */
void GameEngine::writeObjectPositions()
{
    miner->writePosition(totalFrameCounter);

    for (int i = 0;i < asteroidList.size();i++)
        asteroidList[i]->writePosition(totalFrameCounter);

    for (int i = 0;i < collectibleList.size();i++)
        collectibleList[i]->writePosition(totalFrameCounter);

    for (int i = 0;i < enemyList.size();i++)
        enemyList[i]->writePosition(totalFrameCounter);

    if (blackhole)
        blackhole->writePosition(totalFrameCounter);

    if (comet)
        comet->writePosition(totalFrameCounter);
}

/*! 
 * Initialize game objects for the rewind mode playback.
 * The \c GameObject::initRewindMode() method is called for each game object to
 * set the read index for rewind mode data.
 *
 * The rotation angle for asteroids and direction for missiles are also initialized.
 *
 * The particle effects are also initialized by setting the initial position and velocity values. 
 *
 * Particle buffer reading positions are computed and set for each frame. 
 *
 * Sets the stored ship mode for given rewind mode playback start frame.
 *
 * \param firstFrame The starting frame for the rewind mode playback.
 */
void GameEngine::initObjectsForRewindMode(int firstFrame)
{
    miner->initRewindMode(firstFrame);

    for (int i = 0;i < asteroidList.size();i++) {
        asteroidList[i]->initRewindMode(firstFrame);

        // reset the asteroid rotation angles.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // this is not working exactly right, but well
        // enough to fulfill the main purpose here which
        // is to make big asteroid cleaving to medium
        // ones look the same as it was in the game.

        // when a big asteroid is cleaved, the medium
        // ones will inherit it's rotation axis and
        // rotation angle -> reset here the rotation
        // angles to the original settings so that
        // if the cleaving happened during rewind
        // period it will appear the same way as
        // it happened originally.

        // also see GameEngine::createAsteroid().

        asteroidList[i]->resetRotationAngle();
    }

    for (int i = 0;i < collectibleList.size();i++)
        collectibleList[i]->initRewindMode(firstFrame);

    for (int i = 0;i < enemyList.size();i++) {
        GameObject *go = enemyList[i];
        go->initRewindMode(firstFrame);

        if (go->objectType() == TypeMissile) {
            Missile *m = static_cast<Missile *>(go);
            m->initRewindDirection(firstFrame);
        }
    }

    if (blackhole != NULL)
        blackhole->initRewindMode(firstFrame);

    if (comet != NULL)
        comet->initRewindMode(firstFrame);

    // initialize the particles by setting
    // the initial position and velocity values.

    // NOTE : handle normal particles only and skip others.

    for (int i = 0;i < SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY;i++) {
        if (particleData().pBufferB[i].size >= 0.0) {
            particleData().pBufferA[i].positionX = particleData().particleBuffer[i].startPosX;
            particleData().pBufferA[i].positionY = particleData().particleBuffer[i].startPosY;
            particleData().particleBuffer[i].velocityX = particleData().particleBuffer[i].startVelX;
            particleData().particleBuffer[i].velocityY = particleData().particleBuffer[i].startVelY;
            particleData().pBufferB[i].friction = particleData().particleBuffer[i].startFrict;
        }
    }

    // compute particle buffer reading positions for each frame.

    QQueue<FrameInfo *>::iterator fi;
    for (fi = frameInfoQ.begin();fi != frameInfoQ.end();fi++) {
        const int frame = (*fi)->frameNumber();

        // start from the end of particle buffer, and find
        // the first entry (if any) which mathes to frame.

        (*fi)->setReadIndexR(-1);   // leave to -1 if we won't find any...

        for (int i = 0;i < SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY;i++) {
            int readIndex = particleData().particleWriteIndex - (i + 1);
            if (readIndex < 0) readIndex += SMASHMINER_CONFIG_PARTICLE_BUFFER_CAPACITY;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (readIndex < 0)
                qFatal("GameEngine::initObjectsForRewindMode() : bad readIndex.");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

            if (particleData().particleBuffer[readIndex].startFrame < 0)
                continue;   // an invalid particle entry...

            if (particleData().particleBuffer[readIndex].startFrame > frame)
                continue;   // the particle is not born yet...

            (*fi)->setReadIndexR(readIndex);
            break;
        }
    }

    // set the stored spaceship mode for firstFrame.

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (frameInfoQ.head()->frameNumber() != firstFrame) 
        qDebug() << "GameEngine::initObjectsForRewindMode() : frameInfoQ.head() != firstFrame.";

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (frameInfoQ.head()->mode() == FrameInfo::BallMode)
        setShipModeImmediate(SpaceCraftBallMode);
    else
        setShipModeImmediate(SpaceCraftConeMode);
}

/*! 
 * Reset timers for the rewind mode playback.
 *
 * Stops hyper jump timers and cleans up static hyper jump data from \c TimerManager.
 * Hyper jump effects should not be running when rewind mode is set since miner should
 * be protected during hyper jump effects and rewind mode is called when miner dies.
 */
void GameEngine::resetTimersForRewindMode()
{
    // handle the game states.
    //^^^^^^^^^^^^^^^^^^^^^^^^^
    // we should NOT have any of the HyperJump effects running,
    // because the spaceship should be protected against any
    // threats during those effects.

    const int hjInTimer = timerManager->time(GameTimer::hyperJumpInTimer);
    if (hjInTimer >= 0 || TimerManager::hyperJumpInData != NULL) {
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        qFatal("GameEngine::resetTimersForRewindMode() : HyperJumpIn problem : Hyperjump in timer=%d X:%f Y:%f",
            hjInTimer, TimerManager::hyperJumpInData->posX(), TimerManager::hyperJumpInData->posY());
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        timerManager->stopGameTimer(GameTimer::hyperJumpInTimer);
        if (TimerManager::hyperJumpInData != NULL) {
            delete TimerManager::hyperJumpInData;
            TimerManager::hyperJumpInData = NULL;
        }
    }

    const int hjOutTimer = timerManager->time(GameTimer::hyperJumpOutTimer);
    if (hjOutTimer >= 0 || TimerManager::hyperJumpOutData != NULL) {
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        qFatal("GameEngine::resetTimersForRewindMode() : HyperJumpOut problem : : Hyperjump out timer=%d X:%f Y:%f",
            hjOutTimer, TimerManager::hyperJumpOutData->posX(), TimerManager::hyperJumpOutData->posY());
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
        timerManager->stopGameTimer(GameTimer::hyperJumpOutTimer);
        if (TimerManager::hyperJumpOutData != NULL) {
            delete TimerManager::hyperJumpOutData;
            TimerManager::hyperJumpOutData = NULL;
        }
    }
}

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

/*!
 * Set object to ''dead'' state waiting for the possible rewind playback.
 *
 * Note : we may NOT delete objects here since we need 
 * them in the rewind mode after setting object dead!
 *
 * \param gameObject game object being set to ''dead'' state
 * \param frame Current frame as in totalFrameCounter to be stored for the rewind playback
 * \param time game time as in totalTimeCounter
 */
void GameEngine::setObjectDead(GameObject *gameObject, int frame, FPtype time)
{
    gameObject->setDead(frame, time);
}

/// Used as a comparator for qSort 
bool initNextLevelMoreThan(const int i1, const int i2)
{
    return i1 > i2;
}

/*! \brief Initializes the level data.
 *
 * Calls the \c GameLevel instance to write the level data for the next level to \c levelData.
 * Manage the trigger lists for triggering new game objects at radomly selected
 * level progress points, e.g.
 * \code
 * for (int i=0;i < levelData.ufos;i++)
 *     triggerUfo.append(rand() % maxProgress);
 * qSort(triggerUfo.begin(), triggerUfo.end(), initNextLevelMoreThan);
 * \endcode
 */
void GameEngine::initNextLevel()
{
    level->nextLevel(levelData, currentLevelProgress);

    // each large asteroid can be splitted to three medium ones,
    // and each of them are splitted further -> there are 4 splitting
    // events total. compute final the probability 2x higher than theoretical.

    bombProbability = 2.0f * (GLfloat) levelData.bombs / (GLfloat) (levelData.asteroids * 4);

    hyperJumpCount += levelData.hyperjumps;

    // clear all trigger lists.

    triggerUfo.clear();
    triggerMeteorShower.clear();
    triggerBlackHole.clear();
    triggerComet.clear();

    // fill the trigger lists so that the enemies
    // would be randomly created before the level is
    // completed.

    const int maxProgress = currentLevelProgress[1] * 0.80f;

    // fill the trigger lists with random progress values and
    // sort them in INVERTED order.

    for (int i=0;i < levelData.ufos;i++)
        triggerUfo.append(rand() % maxProgress);
    qSort(triggerUfo.begin(), triggerUfo.end(), initNextLevelMoreThan);

    for (int i=0;i < levelData.meteorShowers;i++)
        triggerMeteorShower.append(rand() % maxProgress);
    qSort(triggerMeteorShower.begin(), triggerMeteorShower.end(), initNextLevelMoreThan);

    for (int i=0;i < levelData.blackholes;i++)
        triggerBlackHole.append(rand() % maxProgress);
    qSort(triggerBlackHole.begin(), triggerBlackHole.end(), initNextLevelMoreThan);

    for (int i=0;i < levelData.comets;i++)
        triggerComet.append(rand() % maxProgress);
    qSort(triggerComet.begin(), triggerComet.end(), initNextLevelMoreThan);

    // reset the timers.

    timerManager->resetTimers();
    timerManager->cleanData();
}

/*! Updates the score digits according to game score.
 *
 * \returns QList of score digits as integers.
 */
QList<int> GameEngine::score()
{
    if (gameScore > 999999)
        gameScore = 999999;

    QList<int> scoreList;

    int tmpScore = gameScore;
    int tmp = tmpScore / 100000;
    tmpScore -= 100000 * tmp;
    scoreList.append(tmp);

    tmp = tmpScore / 10000;
    tmpScore -= 10000 * tmp;
    scoreList.append(tmp);

    tmp = tmpScore / 1000;
    tmpScore -= 1000 * tmp;
    scoreList.append(tmp);

    tmp = tmpScore / 100;
    tmpScore -= 100 * tmp;
    scoreList.append(tmp);

    tmp = tmpScore / 10;
    tmpScore -= 10 * tmp;
    scoreList.append(tmp);

    scoreList.append(tmpScore);

    return scoreList;
}

/*! Populates game level with new game objects.
 *
 * If the game objectives for current level are not already filled, new game
 * objects may be appended to game. Game level objectives are tested with 
 * \c GameEngine::objectivesFilled().
 *
 * The game timer identified with \c GameTimer::moreObjectsTimer
 * regulates when new objects are appended to game level. When the timer has full
 * time new game objects may be added and the timer is restarted.
 *
 * \code
 * if (!timerManager->isFullTime(GameTimer::moreObjectsTimer))
 *      return;
 * timerManager->startGameTimer(GameTimer::moreObjectsTimer);
 * \endcode
 *
 * Each level has maximum amount of game objects in screen stored in \c levelData 
 * (\c GameLevelData::maxObjectsInScreen). \c levelData has also the remaining amount of
 * each game object for current level, e.g. \c levelData.asteroids.
 *
 * \c currentObjects constains the amount of current game objects on screen and \c
 * potentialObjects contains the potential amount of game objects resulting from current objects.
 * These values are calculated while checking if the game objectives are already filled with
 * \c GameEngine::objectivesFilled().
 * 
 * A game object of type T is added to screen if game objects of type T are remaining for current level and if 
 * the potential game objects resulting from game object of type T does not pass maximum amount of objects in screen defined for 
 * current level. 
 *
 * Also the current level progress is checked (If the game object is other than asteroid).
 * The game objects are triggered to screen on randomly selected progress points. The trigger points
 * are set in \c GameEngine::initNextLevel() when initializing a new level.
 *
 * Array \c GameEngine::currentLevelProgress contains the current level progress value in index 0 and the
 * maximum progress value in index 1. The maximum progress value for level is counted in \c GameLevel::nextLevel(). 
 *
 * \code
 * if (levelData.ufos > 0 && potentialObjects + objectValue <= levelData.maxObjectsInScreen
 *     && currentLevelProgress[0] >= triggerUfo.at(levelData.ufos - 1)) {
 *      levelData.ufos--;
 *      currentObjects++;
 *      potentialObjects += objectValue;
 *      // ...
 *      Ufo *u = new Ufo(totalFrameCounter, rewindEnabled, Ufo::SineMovement, sp.angle);
 *      // ...
 *      enemyList.append(u);
 *  }
 * \endcode
 *
 * After adding a new game object the currentObjects count is increased and the
 * potentialObjects value is appended with the amount of potential objects resulting from object
 * of type T (as defined in enumeration \c PotentialObjects). An object of type T is instantiated
 * and added to game.
 *
 */
void GameEngine::moreGameObjects()
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (currentLevelProgress[1] < 1)
        qFatal("GameEngine::moreGameObjects() : strange maxProgress %d", currentLevelProgress[1]);

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    // if moreObjectsTimer is not running, then start it, since
    // it should cycle constantly when we are in a normal game state.

    if (timerManager->time(GameTimer::moreObjectsTimer) < 0)
        timerManager->startGameTimer(GameTimer::moreObjectsTimer);

    // if all the objectives of this level are filled,
    // we will switch into LevelChanging state and nothing
    // needs to be done here.

    int currentObjects;
    int potentialObjects;

    if (objectivesFilled(currentObjects, potentialObjects))
        return;

    // now just wait until moreObjectsTimer event appears...

    if (!timerManager->isFullTime(GameTimer::moreObjectsTimer))
        return;

    // remember to restart the moreObjectsTimer immediately!

    timerManager->startGameTimer(GameTimer::moreObjectsTimer);

    int objectValue;

    // check Asteroids...

    objectValue = PotentialObjectsLargeAsteroid;
    if (levelData.asteroids > 0
            && potentialObjects + objectValue <= levelData.maxObjectsInScreen) {
        levelData.asteroids--;

        currentObjects++;
        potentialObjects += objectValue;

        // create an Asteroid object.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        Asteroid *a = createAsteroid(TypeAsteroidLarge);
        asteroidList.append(a);
    }

    // check UFOs...

    objectValue = PotentialObjectsOther;
    if (levelData.ufos > 0
            && potentialObjects + objectValue <= levelData.maxObjectsInScreen
            && currentLevelProgress[0] >= triggerUfo.at(levelData.ufos - 1)) {
        levelData.ufos--;

        currentObjects++;
        potentialObjects += objectValue;

        // create an UFO object.
        //^^^^^^^^^^^^^^^^^^^^^^^

        Ufo * u = NULL;
        GameObjectStartParameters sp;
        if (getRandomF() < 0.50f) {

            // for SineMovement use margin = 1.25 which is a normal value.

            getStartParams(AnyDirection, 1.25f, sp);
            u = new Ufo(totalFrameCounter, rewindEnabled, Ufo::SineMovement, sp.angle);
        } else {

            // for CircularMovement use margin = 1.00 which is a smaller value.
            // this is to compensate the "backward" movement involved in CircularMovement.
            // if the complensation is not large enough, the UFO may be removed immediately.
            // see GameEngine::removeUnnecessaryObjects() and GameEngine::getStartParams().

            getStartParams(AnyDirection, 1.00f, sp);
            u = new Ufo(totalFrameCounter, rewindEnabled, Ufo::CircularMovement, sp.angle);
        }

        u->setPosX(sp.startX);
        u->setPosY(sp.startY);

        enemyList.append(u);
    }

    // check MeteorShowers...

    objectValue = PotentialObjectsOther;
    if (levelData.meteorShowers > 0
            && potentialObjects + objectValue <= levelData.maxObjectsInScreen
            && currentLevelProgress[0] >= triggerMeteorShower.at(levelData.meteorShowers - 1)) {
        levelData.meteorShowers--;

        currentObjects++;
        potentialObjects += objectValue;

        // create MeteorShower objects.
        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        GameObjectStartParameters sp;
        getStartParams(AnyDirection, 1.25f, sp);

        // WARNING : meteorshower growth rate must be small compared to velocity, so that
        // no free space will appear between the meteors (the player cannot fly there!).

        const GLfloat initialRadius = 10.0f;

        const GLfloat tailDirectionX = sin(sp.angle + M_PI) * initialRadius * 2.2f;
        const GLfloat tailDirectionY = cos(sp.angle + M_PI) * initialRadius * 2.2f;

        const GLfloat sideDirectionX = sin(sp.angle + M_PI * 0.50f) * initialRadius * 2.0f;
        const GLfloat sideDirectionY = cos(sp.angle + M_PI * 0.50f) * initialRadius * 2.0f;

        const FPtype startPos[2] = { sp.startX, sp.startY };
        const FPtype centerPos[2] = { (sp.startX + sp.endX) * 0.5f, (sp.startY + sp.endY) * 0.5f };

        for (int i = 0;i < levelData.meteorShowerSize;i++) {
            MeteorShower *ms = new MeteorShower(totalFrameCounter, rewindEnabled, 
                                                totalTimeCounter, initialRadius,
                                                levelData.meteorShowerRadiusChange);
            ms->setStartAndCenterPositions(startPos, centerPos);

            const GLfloat random = getRandomF() - 0.50f;
            const GLfloat posX = sp.startX + i * tailDirectionX + random * sideDirectionX;
            const GLfloat posY = sp.startY + i * tailDirectionY + random * sideDirectionY;

            ms->setPosX(posX);
            ms->setPosY(posY);

            ms->setVelX(sp.unitVelX * levelData.meteorShowerSpeed);
            ms->setVelY(sp.unitVelY * levelData.meteorShowerSpeed);

            enemyList.append(ms);
        }
    }

    // check BlackHoles...
    // NOTE : multiple BlackHoles may not be shown concurently.
    // skip the BlackHole addition if a previous one exists.

    objectValue = PotentialObjectsOther;
    if (levelData.blackholes > 0
            && potentialObjects + objectValue <= levelData.maxObjectsInScreen
            && currentLevelProgress[0] >= triggerBlackHole.at(levelData.blackholes - 1)) {
        levelData.blackholes--;

        if (blackhole == NULL) {
            currentObjects++;
            potentialObjects += objectValue;

            // create a BlackHole object.
            //^^^^^^^^^^^^^^^^^^^^^^^^^^^^

            GameObjectStartParameters sp;
            getStartParams(HorizontalDirection, 1.25f, sp);

            blackhole = new BlackHole(totalFrameCounter, rewindEnabled, levelData.blackholeRadius);

            blackhole->setPosX(sp.startX);
            blackhole->setPosY(sp.startY);

            blackhole->setVelX(sp.unitVelX * levelData.blackholeSpeed);
            blackhole->setVelY(sp.unitVelY * levelData.blackholeSpeed);
        }
    }

    // check Comets...
    // NOTE : multiple Comets may not be shown concurently.
    // skip the Comet addition if a previous one exists.

    objectValue = PotentialObjectsOther;
    if (levelData.comets > 0
            && potentialObjects + objectValue <= levelData.maxObjectsInScreen
            && currentLevelProgress[0] >= triggerComet.at(levelData.comets - 1)) {
        levelData.comets--;

        if (comet == NULL) {
            currentObjects++;
            potentialObjects += objectValue;

            // create a Comet object.
            //^^^^^^^^^^^^^^^^^^^^^^^^

            GameObjectStartParameters sp;
            getStartParams(AnyDirection, 1.25f, sp);

            // WARNING : comet growth rate(s) must be small compared to it's velocity.
            // if it grows faster than it moves, it will never get out of the screen!

            comet = new Comet(totalFrameCounter, rewindEnabled, totalTimeCounter, 
                              levelData.cometLengthChange, levelData.cometWidth, 
                              levelData.cometWidthChange);

            comet->setPosX(sp.startX);
            comet->setPosY(sp.startY);

            comet->setRotationAngle(sp.angle);

            comet->setVelX(sp.unitVelX * levelData.cometSpeed);
            comet->setVelY(sp.unitVelY * levelData.cometSpeed);
        }
    }
}

/*!
 * Determines start parameters for a game object: start and end location,
 * direction vector as unit vector and rotation angle.
 *
 * If direction is not defined (\c GameObjectStartMode::AnyDirection) the direction is randomized
 * between horizontal and vertical direction.
 *
 * Determine a startpoint and an endpoint from the opposite edges of the game viewing area.
 * When creating a horizontal direction the positive and negative view X-range values are used by adjusting
 * values with \c marginRelative factor (usually to achieve off-screen locations):
 *
 * \code
 * startX = -marginRelative * gameViewRangeX;
 * endX = +marginRelative * gameViewRangeX;
 * \endcode
 *
 * The starting Y-location is factor is calculated by adding \c random value to \c shift value. 
 * The \c endY factor value is calculated by subtracting \c random value from \c shift value: 
 * \code
 * shift = 0.7f * (getRandomF() - 0.50f);
 * random = 1.4f * (getRandomF() - 0.50f);
 * startY = shift + random;
 * endY = shift - random;
 * \endcode
 *
 * When calculating horizontal direction the start and end Y-coordinates must not be off-screen 
 * locations. The \c startY and \c endY values are checked agains \c limit value to prevent off-screen
 * values: 
 * \code
 * if (startY < -limit) startY = -limit;
 * if (startY > +limit) startY = +limit;
 * \endcode
 *
 * Start and end Y-locations are achieved by multiplying the view Y-range with \c startY and
 * \c endY factors:
 * \code
 * startY *= gameViewRangeY;
 * endY *= gameViewRangeY;
 * \endcode
 *
 * Vertical start direction is calculated accordingly.
 *
 * Direction vector is scaled into an unit vector and start angle is calculated according to 
 * direction unit vector.
 *
 * \param mode              One of the modes defined with enumeration ::GameObjectStartMode.
 * \param marginRelative    Relative margin for calculating location for game object. Margin
 *                          values > 1.0 (e.g. 1.15) are used for computing off-screen position 
 *                          for the game object.

 * \param[out] startParameters Structure for storing game object start parameters.
 */
void GameEngine::getStartParams(GameObjectStartMode mode, FPtype marginRelative, 
                                GameObjectStartParameters &startParameters)
{
    if (mode == AnyDirection) {
        if (getRandomF() < 0.50f)
            mode = HorizontalDirection;
        else
            mode = VerticalDirection;
    }

    // determine a startpoint and an endpoint from
    // the opposite edges of the game viewing area.

    // see GameEngine::removeUnnecessaryObjects() on
    // how the screen coordinates are used to remove
    // unnecessary non-wrapping (enemy) objects from
    // the game. the "margin" used here must be lower
    // than the one used when removing objects.

    // setting marginRelative < 1.0 will make the starting
    // positions appear directly on screen, which may
    // be helpful in debugging...

    FPtype startX;
    FPtype startY;

    FPtype endX;
    FPtype endY;

    // keep "shift" always smaller than "random".
    const FPtype shift = 0.7f * (getRandomF() - 0.50f);
    const FPtype random = 1.4f * (getRandomF() - 0.50f);

    // do not let the positions go off-screen.
    const FPtype limit = 0.90f;

    if (mode == HorizontalDirection) {
        if (getRandomF() < 0.50f) {
            startX = -marginRelative * gameViewRangeX;
            endX = +marginRelative * gameViewRangeX;
        }
        else {
            startX = +marginRelative * gameViewRangeX;
            endX = -marginRelative * gameViewRangeX;
        }

        startY = shift + random;
        if (startY < -limit) startY = -limit;
        if (startY > +limit) startY = +limit;

        endY = shift - random;
        if (endY < -limit) endY = -limit;
        if (endY > +limit) endY = +limit;

        startY *= gameViewRangeY;
        endY *= gameViewRangeY;
    }
    else {
        if (getRandomF() < 0.50f) {
            startY = -marginRelative * gameViewRangeY;
            endY = +marginRelative * gameViewRangeY;
        }
        else {
            startY = +marginRelative * gameViewRangeY;
            endY = -marginRelative * gameViewRangeY;
        }

        startX = shift + random;
        if (startX < -limit) startX = -limit;
        if (startX > +limit) startX = +limit;

        endX = shift - random;
        if (endX < -limit) endX = -limit;
        if (endX > +limit) endX = +limit;

        startX *= gameViewRangeX;
        endX *= gameViewRangeX;
    }

    startParameters.startX = startX;
    startParameters.startY = startY;

    startParameters.endX = endX;
    startParameters.endY = endY;

    // determine the unit velocity vector and direction.

    startParameters.unitVelX = endX - startX;
    startParameters.unitVelY = endY - startY;

    const FPtype scaling = sqrt(startParameters.unitVelX * startParameters.unitVelX 
        + startParameters.unitVelY * startParameters.unitVelY);

    startParameters.unitVelX /= scaling;
    startParameters.unitVelY /= scaling;

    startParameters.angle = acos(startParameters.unitVelY);
    if (startParameters.unitVelX < 0.0f)
        startParameters.angle = 2.0f * M_PI - startParameters.angle;

#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    FPtype test1;
    FPtype test2;

    bool error = false;

    // the value 250.0 below is just an approximate screen
    // diagonal distance, used for estimating the trajectory end points.
    // the value itself is not important, but it must be a constant.

    test1 = startX + startParameters.unitVelX * 250.0f;
    test2 = startX + sin(startParameters.angle) * 250.0f;
    if (fabs(test2 - test1) > 0.1f)
        error = true;

    test1 = startY + startParameters.unitVelY * 250.0f;
    test2 = startY + cos(startParameters.angle) * 250.0f;
    if (fabs(test2 - test1) > 0.1f)
        error = true;

    // if the results based on angle and unit vectors seem to differ,
    // show an error message and leave...

    if (error)
        qFatal("GameEngine::getStartParams() : unit/angle mismatch "
                "sp.unitVelX = %f, "
                "sp.unitVelY = %f, "
                "sp.angle = %f.",
                startParameters.unitVelX,
                startParameters.unitVelY,
                startParameters.angle);

    // store the result for later graphical display.

    GameObjectStartParameters store;

    store.startX = startParameters.startX;
    store.startY = startParameters.startY;

    store.angle = startParameters.angle;
    store.unitVelX = startParameters.unitVelX;
    store.unitVelY = startParameters.unitVelY;

    store.endX = startParameters.endX;
    store.endY = startParameters.endY;

    storedStartParamsDEBUG.append(store);

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
}

/*!
 * Creates a bomb collectible and appends it to the list of collectibles.
 *
 * \param asteroid A ''host'' asteroid for bomb, the initial location of the bomb.
 */
void GameEngine::createBomb(const Asteroid *asteroid)
{
    Collectible *c = createCollectible(TypeCollectibleBomb, asteroid);
    collectibleList.append(c);
}

/*! Applies a bomb destruction effect on game objects according to bomb effect state (location and
 * ''shock wave''-radius).
 *
 * Bomb effect data (location and radius) is stored as static data to TimerManager::bombEffectData.
 *
 * When checking if bomb destruction will be applied on a game object, the distance square (to
 * prevent calculating root square) between game object and bomb effect is calculated.
 * If the distance is inside the shock wave radius, the game object will be set to ''dead''-state.
 * The player's score will be incremented according to the score value of a game object and a
 * particle explosion effect will be presented. Also the current level progress will be
 * inceremented according to the progress value of the item in question.
 *
 * This method should be called only if \c bombEffectTimer is active.
 * \c bombEffectTimer activates after the player launches a bomb.
 * When the \c bombEffectTimer is active also the \c TimerManager::bombEffectData is assumed to have 
 * valid data.
 */
void GameEngine::applyBombDestruction() 
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (TimerManager::bombEffectData == NULL)
        qFatal("bombEffectTimer : bombEffectData == NULL");

#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    const FPtype x = TimerManager::bombEffectData->posX();
    const FPtype y = TimerManager::bombEffectData->posY();

    const FPtype r = TimerManager::bombEffectData->shockwaveRadius();
    const FPtype rSqr = r * r;

    // calculate the distances of game objects to bomEffect, if the distance is
    // less than shockwaveRadius, the object will be exploded.

    for (int i = asteroidList.count() - 1; i >= 0; --i) {
        Asteroid *a = asteroidList[i];
        if (!a->isAlive()) continue;
        const FPtype distX = a->posX() - x;
        const FPtype distY = a->posY() - y;
        const FPtype distSqr = distX * distX + distY * distY;
        if (distSqr < rSqr) {
            gameScore += a->scoreValue();
            createParticleExplosion(a->posX(), a->posY(), 30);

            if (a->objectType() == TypeAsteroidLarge)
                currentLevelProgress[0] += ProgressLargeAsteroid;
            else
                currentLevelProgress[0] += ProgressMediumAsteroid;

            setObjectDead(a, totalFrameCounter, totalTimeCounter);
        }
    }

    for (int i = collectibleList.count() - 1; i >= 0; --i) {
        Collectible *c = collectibleList[i];
        if (!c->isAlive()) continue;
        const FPtype distX = c->posX() - x;
        const FPtype distY = c->posY() - y;
        const FPtype distSqr = distX * distX + distY * distY;
        if (distSqr < rSqr) {
            gameScore += c->scoreValue();
            createParticleExplosion(c->posX(), c->posY(), 30);

            bool isMineral = false;
            if (c->objectType() == TypeCollectibleMineralA)
                isMineral = true;
            if (c->objectType() == TypeCollectibleMineralB)
                isMineral = true;
            if (c->objectType() == TypeCollectibleMineralC)
                isMineral = true;
            if (c->objectType() == TypeCollectibleMineralD)
                isMineral = true;
            if (isMineral)
                currentLevelProgress[0] += ProgressOther;

            setObjectDead(c, totalFrameCounter, totalTimeCounter);
        }
    }

    for (int i = enemyList.count() - 1; i >= 0; --i) {
        GameObject *go = enemyList[i];
        if (!go->isAlive()) continue;
        const FPtype distX = go->posX() - x;
        const FPtype distY = go->posY() - y;
        const FPtype distSqr = distX * distX + distY * distY;
        if (distSqr < rSqr) {
            gameScore += go->scoreValue();
            createParticleExplosion(go->posX(), go->posY(), 30);
            setObjectDead(go, totalFrameCounter, totalTimeCounter);
        }
    }

    if (blackhole && blackhole->isAlive()) {
        const FPtype distX = blackhole->posX() - x;
        const FPtype distY = blackhole->posY() - y;
        const FPtype distSqr = distX * distX + distY * distY;
        if (distSqr < rSqr) {
            gameScore += blackhole->scoreValue();
            createParticleExplosion(blackhole->posX(), blackhole->posY(), 50);
            setObjectDead(blackhole, totalFrameCounter, totalTimeCounter);
        }
    }

    if (comet && comet->isAlive()) {
        const FPtype distX = comet->posX() - x;
        const FPtype distY = comet->posY() - y;
        const FPtype distSqr = distX * distX + distY * distY;
        if (distSqr < rSqr) {
            gameScore += comet->scoreValue();
            createParticleExplosion(comet->posX(), comet->posY(), 50);
            setObjectDead(comet, totalFrameCounter, totalTimeCounter);
        }
    }
}

/*! Fires an Ufo missile.
 *
 * New \c Missile object is instantiated with guidance and speed factor from level data
 * (\c levelData.missileGuidance and \c levelData.missileSpeed). Missile gets the start
 * position from the Ufo object. The \c GameEngine::steerMissile() is called with \c reset=true argument to 
 * initialize the missile direction. Missile object is appended to \c enemyList.
 *
 * \param ufo The \c Ufo instance firing a missile.
 */
void GameEngine::fireMissile(const Ufo *ufo)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    if (!ufo || !ufo->isAlive()) 
        qFatal("A non existing or dead UFO is trying to fire missiles.");
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    Missile *m = new Missile(totalFrameCounter, rewindEnabled,
                             levelData.missileGuidance,
                             levelData.missileSpeed);
    m->setPosX(ufo->posX());
    m->setPosY(ufo->posY());
    steerMissile(m, true);
    enemyList.append(m);
}

/*! Steers the Ufo missile.
 *
 * A direction vector from missile to miner is calculated from X- and Y-coordinates.
 * If reset was called the missile direction is initialized with \c Missile::initDirection() 
 * using calculated direction vector. Otherwise missile direction is adjusted according to 
 * direction vector.
 *
 * \param missile   The \c Missile instance being steered
 * \param reset     If set to ''true'' the missile direction is initialized 
 *                  otherwise the missile direction is adjusted.
 */
void GameEngine::steerMissile(Missile *missile, bool reset)
{
#ifdef SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS
    if (!missile)
        qFatal("GameEngine::steerMissile: A non existing or dead missile.");
#endif  // SMASHMINER_CONFIG_ENABLE_DEBUG_CHECKS

    if (!missile->isAlive()) return;

    // Calculate direction from missile to miner.
    FPtype dir[2] = {
        miner->posX() - missile->posX(),
        miner->posY() - missile->posY()
    };

    if (reset)
        missile->initDirection(dir);
    else
        missile->adjustDirection(dir);
}

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

void GameEngine::setRewindEnabled(bool rewind)
{
    if (rewind == rewindEnabled)
        return;

    // change the rewindEnabled state.

    rewindEnabled = rewind;

    // pass the change to all GameObjects.
    // NOTE : no need to check whether the object is dead or alive.

    miner->setRewindEnabled(rewind);

    const int astCount = asteroidList.count();
    for (int i = 0;i < astCount;i++)
        asteroidList.at(i)->setRewindEnabled(rewind);

    const int colCount = collectibleList.count();
    for (int i = 0;i < colCount;i++)
        collectibleList.at(i)->setRewindEnabled(rewind);

    const int eneCount = enemyList.count();
    for (int i = 0;i < eneCount;i++)
        enemyList.at(i)->setRewindEnabled(rewind);

    if (blackhole != NULL)
        blackhole->setRewindEnabled(rewind);

    if (comet != NULL)
        comet->setRewindEnabled(rewind);

    // cleanup the frameinfo queue.

    cleanUpRewindFrames(false);
}

/*! Cleans up the rewind mode queue. Dequeues the \c FrameInfo objects
 * and deletes them. Cleans up also game objects if requested.
 *
 * \param cleanUpAlsoGameObjects If set to true the game objects will be also cleaned up.
 */
void GameEngine::cleanUpRewindFrames(bool cleanUpAlsoGameObjects)
{
    while (frameInfoQ.size() > 1) {
        FrameInfo *info = frameInfoQ.dequeue();
        delete info;
    }

    if (cleanUpAlsoGameObjects)
        cleanUpGameObjects(false);
}

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

/*! Cleans up all the dynamically created game objects.
 * Asteroid, collectible and enemy lists will be cleaned up and the 
 * objects are deleted. Blackhole and comet objects are deleted. 
 * If a ''complete cleanup'' is requested also the \c miner, \c level 
 * and \c hyperJumpNavigator objects will be deleted.
 *
 * \param complete If set to true a complete cleanup for dynamic objects will be executed.
 */
void GameEngine::cleanUpGameObjects(bool complete)
{
    Asteroid *a;
    for (int i = asteroidList.count() - 1; i >= 0; --i) {
        a = asteroidList[i]; 
        asteroidList.removeAt(i);
        delete a;
    }
    a = NULL;

    Collectible *c;
    for (int i = collectibleList.count() - 1; i >= 0; --i) {
        c = collectibleList[i];
        collectibleList.removeAt(i);
        delete c;
    }
    c = NULL;

    GameObject *go;
    for (int i = enemyList.count() - 1; i >= 0; --i) {
        go = enemyList[i];
        enemyList.removeAt(i);
        delete go;
    }
    go = NULL;

    if (comet != NULL) {
        delete comet;
        comet = NULL;
    }

    if (blackhole != NULL) {
        delete blackhole;
        blackhole = NULL;
    }

    // if a "complete" cleanup is not requested, then stop here.
    // an "incomplete" cleanup is requested when moving to a new gamelevel.
    // see GameEngine::cleanUpRewindFramesAndGameObjects() above.

    if (complete) {
        if (level != NULL) {
            delete level;
            level = NULL;
        }

        if (hyperJumpNavigator != NULL) {
            delete hyperJumpNavigator;
            hyperJumpNavigator = NULL;
        }

        if (miner != NULL)
        {
            delete miner;
            miner = NULL;
        }
    }
}

/*! Checks if the objectives for current level are fulfilled.
 *
 * The game objects in screen will be counted and the potential game object count is measured
 * according to existing game objects.
 *
 * The game level objectives are fulfilled if miner is alive, there's no game objects in 
 * screen and all remaining asteroids for level is 0. If the objectives are fulfilled, 
 * the timer for changing to a new level is started (\c GameTimer::stateLevelChangingTimer).
 *
 * \param currentObjects    To store the current objects in screen count
 * \param potentialObjects  To store the potential objects in screen count 
 *                          reflecting from current game objects in screen
 *
 * \return True if all the objectives for the level are fulfilled.
 */
bool GameEngine::objectivesFilled(int &currentObjects, int &potentialObjects) const
{
    currentObjects = 0;
    potentialObjects = 0;

    // count how many game objects (other than miner) are alive.

    // also estimate how many game objects could exist if the
    // currently existing asteroids were splitted etc...

    for (int i = 0; i < asteroidList.size(); ++i) {
        if (asteroidList[i]->isAlive()) {
            currentObjects++;
            if (asteroidList[i]->objectType() == TypeAsteroidLarge)
                potentialObjects += PotentialObjectsLargeAsteroid;
            else
                potentialObjects += PotentialObjectsMediumAsteroid;
        }
    }

    for (int i = 0; i < collectibleList.size(); ++i) {
        if (collectibleList[i]->isAlive()) {
            currentObjects++;
            potentialObjects += PotentialObjectsOther;
        }
    }

    for (int i = 0; i < enemyList.size(); ++i) {
        if (enemyList[i]->isAlive()) {
            currentObjects++;
            potentialObjects += PotentialObjectsOther;
        }
    }

    if (blackhole && blackhole->isAlive()) {
        currentObjects++;
        potentialObjects += PotentialObjectsOther;
    }

    if (comet && comet->isAlive()) {
        currentObjects++;
        potentialObjects += PotentialObjectsOther;
    }

    // check if we need to change the level.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // the game level is changed when
    // 1) we are in a "normal game" state and the player is alive.
    // 2) no enemies (or any other GameObjects) remain at the screen anymore.
    // 3) all objectives of the current level are reached.

    bool objectivesFilled = true;

    if (!miner->isAlive())
        objectivesFilled = false;

    if (currentObjects > 0)
        objectivesFilled = false;

    if (levelData.asteroids > 0)
        objectivesFilled = false;

    // changing the level means that a LevelChanging timer is started.
    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // the timer will do the following tasks:
    // 1) after a small delay, the background image is changed.
    // 2) a new level text(ure) is shown to the player.
    // 3) initNextLevel() is called to initialize the level management.

    if (objectivesFilled)
        timerManager->startGameTimer(GameTimer::stateLevelChangingTimer);

    return objectivesFilled;
}
